这种行为没有有很好的记录,并且存在于从大约 1.5-ish 到Python 3.4的所有 Python 版本中:
作为此更改的一部分,在大多数情况下,在解释器关闭期间模块全局变量不再强制设置为 None,而是依赖于循环垃圾收集器的正常操作。
该行为的唯一文档是moduleobject.c source code:
/* To make the execution order of destructors for global
objects a bit more predictable, we first zap all objects
whose name starts with a single underscore, before we clear
the entire dictionary. We zap them by replacing them with
None, rather than deleting them from the dictionary, to
avoid rehashing the dictionary (to some extent). */
请注意,将值设置为 None 是一种优化;另一种方法是从映射中删除名称,这将导致不同的错误(NameError 异常而不是 AttributeErrors 在尝试使用来自 __del__ 处理程序的全局变量时)。
正如您在邮件列表中发现的那样,这种行为早于循环垃圾收集器;它是added in 1998,而循环垃圾收集器是added in 2000。由于函数对象总是引用模块__dict__,模块中的所有函数对象都涉及循环引用,这就是为什么__dict__需要在GC之前清除。
即使添加了循环 GC,它也会保留在原位,因为循环中可能存在具有 __del__ 方法的对象。这些aren't otherwise garbage-collectable 和清除模块字典至少会从此类循环中删除模块__dict__。不这样做将使该模块的 所有 引用的全局变量保持活动状态。
对PEP 442 所做的更改现在使垃圾收集器可以清除带有提供__del__ 终结器的对象的循环引用,从而无需清除模块__dict__ 在大多数情况下时间>。代码为still there,但仅当__dict__ 属性仍然存在时才会触发,即使在将sys.modules 的内容移动到弱引用并在解释器关闭时启动GC 收集运行之后;模块终结器只是减少它们的引用计数。