Python 2.7 可能的错误,导入的模块“消失”
我发现了一些很奇怪的事情。看看下面这段简短的代码。
import os
class Logger(object):
def __init__(self):
self.pid = os.getpid()
print "os: %s." %os
def __del__(self):
print "os: %s." %os
def temp_test_path():
return "./[%d].log" %(os.getpid())
logger = Logger()
这段代码是用来举例的。它只是打印了导入的模块 os
,在一个类的创建和销毁时(别在意这个类叫 Logger
)。但是,当我运行这段代码时,模块 os
在类的销毁时似乎“消失”了,变成了 None
。下面是输出结果。
os: <module 'os' from 'C:\Python27\lib\os.pyc'>.
os: None.
我遇到的问题就是上面提到的 os: None.
。它应该和第一行输出是一样的。然而,回头看看上面的 Python 代码,在函数 temp_test_path()
中。如果我稍微改一下这个函数的名字,比如改成 temp_test_pat()
,然后保持其他代码完全不变,再运行一次,我就得到了预期的输出(如下)。
os: <module 'os' from 'C:\Python27\lib\os.pyc'>.
os: <module 'os' from 'C:\Python27\lib\os.pyc'>.
我找不到任何解释,除了这可能是个 bug。你能找到吗?顺便说一下,我使用的是 Windows 7 64 位系统。
4 个回答
其他人已经给出了答案,在模块关闭时,全局变量(比如 os
、Logger
和 logger
)被删除的顺序是没有定义的,也就是说你不能确定它们会先后被删除。
不过,如果你想找到一个解决办法,可以在清理函数的本地命名空间中导入 os
:
def __del__(self):
import os
print "os: %s." %os
在这个时候,os
模块仍然存在,只是你失去了对它的全局引用。
我复现了这个现象,确实很有趣。你需要明白的一点是,__del__
这个方法并不一定会在解释器退出时被调用。而且,在解释器退出时,清理对象的顺序也是没有规定的。
因为你要退出解释器,所以不能保证 os
是不是已经被先删除了。在这种情况下,看起来 os
确实是在你的 Logger
对象之前被清理掉的。这些事情可能是根据 globals
字典中的顺序发生的。
如果我们在退出之前打印一下全局字典的键:
for k in globals().keys():
print k
你会看到:
temp_test_path
__builtins__
__file__
__package__
__name__
Logger
os
__doc__
logger
或者:
logger
__builtins__
__file__
__package__
temp_test_pat
__name__
Logger
os
__doc__
注意一下你的 logger
在列表中的位置,特别是和 os
的位置相比。使用 temp_test_pat
时,logger
实际上是最先被清理的,这样 os
仍然和某个有意义的东西绑定在一起。然而,在使用 temp_test_path
的情况下,logger
是最后被清理的。
如果你希望一个对象在解释器退出之前一直存在,并且你有一些清理代码想要运行,你可以使用 atexit.register
来注册一个函数,让它在退出时执行。
如果你依赖解释器关闭来调用你的 __del__
方法,可能会出现 os
模块在你的 __del__
被调用之前就已经被删除的情况。你可以试着在代码里明确地写 del logger
,然后稍微等一会儿。这样做应该能清楚地显示出代码是按你预期的那样运行的。
我还想给你推荐一个链接,里面有一段关于 __del__
的说明,来自 官方文档,提到在 CPython 的实现中,__del__
并不一定会被调用。