Python 2.7 可能的错误,导入的模块“消失”

2 投票
4 回答
763 浏览
提问于 2025-04-17 16:31

我发现了一些很奇怪的事情。看看下面这段简短的代码。

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 个回答

3

其他人已经给出了答案,在模块关闭时,全局变量(比如 osLoggerlogger)被删除的顺序是没有定义的,也就是说你不能确定它们会先后被删除。

不过,如果你想找到一个解决办法,可以在清理函数的本地命名空间中导入 os

def __del__(self):
    import os
    print "os: %s." %os

在这个时候,os 模块仍然存在,只是你失去了对它的全局引用。

4

我复现了这个现象,确实很有趣。你需要明白的一点是,__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 来注册一个函数,让它在退出时执行。

4

如果你依赖解释器关闭来调用你的 __del__ 方法,可能会出现 os 模块在你的 __del__ 被调用之前就已经被删除的情况。你可以试着在代码里明确地写 del logger,然后稍微等一会儿。这样做应该能清楚地显示出代码是按你预期的那样运行的。

我还想给你推荐一个链接,里面有一段关于 __del__ 的说明,来自 官方文档,提到在 CPython 的实现中,__del__ 并不一定会被调用。

撰写回答