在探讨Python全局对象的生命周期时,核心结论非常简洁:对象的存活与否,并不取决于它定义在何处,而是取决于是否还有活跃的引用。引用计数机制与垃圾回收器共同决定其命运。在模块卸载之前,全局变量不会自动释放。接下来,我们将逐一深入解析。

位于全局执行上下文中的对象,并不会随着函数退出而自动销毁。它们由谁管理?答案是引用计数与垃圾回收器。判断是否回收的关键标准,就是“是否存在活跃引用”——这一原则远比“定义在何处”更为重要。
全局变量的引用计数行为
一旦创建全局变量,它的引用计数初始值至少为1(来自模块的全局命名空间)。只要变量名没有被del语句移除、没有被重新赋值,并且模块没有被卸载,那么它所指向的对象就会一直存活。
- 赋值操作(例如
a = [1,2,3])会使对象的引用计数增加1 - 使用
del a或a = None会减少原对象的引用计数——如果没有其他引用,则会立即回收 - 当模块重载或解释器退出时,全局命名空间被清理,从而触发批量引用减量
循环引用在全局上下文中的风险更高
局部变量在离开作用域后,引用会自动归零。但全局变量由于长期存活,如果形成了循环引用(比如两个类实例互相持有对方引用),引用计数将永远无法归零,仅靠引用计数机制无法回收。
- 典型场景:在缓存字典中存入带有反向引用的对象。例如
cache['node_a'] = node_a,而node_a.parent = node_b,node_b.children.append(node_a)——这就构成了死循环引用 - 这类对象会滞留在内存中,直到标记-清除阶段介入(通常由分代回收触发)
- 可以手动调用
gc.collect(),或者调整gc.set_threshold()来改变垃圾回收的灵敏度
模块级对象与内存释放时机
模块本身是一个全局对象。其中在顶层定义的变量、函数、类,在模块没有卸载之前都不会被回收。即便函数内部创建了大量数据并绑定到全局名称,这些数据也不会随着函数返回而释放。
- 举个例子:
global data; data = list(range(10**7))—— 内存占用会持续存在 - 如何释放?必须显式切断所有引用:
del data或data = None,同时确认没有其他变量、容器、闭包等间接引用它 - 如果导入的模块不再使用,可以用
sys.modules.pop('module_name')辅助卸载(但需要谨慎,可能影响其他依赖模块)
调试与验证方法
判断全局对象是否已被回收,不能仅看变量名是否存在,而应当关注它指向的对象是否还有有效的引用。
- 使用
sys.getrefcount(obj)查询当前引用计数(注意:调用本身会临时增加1个引用) - 使用
gc.get_referrers(obj)查找哪些对象正在引用它——排查循环引用时特别有用 - 结合
psutil.Process().memory_info().rss观察实际内存变化,避免被缓存或延迟回收所误导
