在Python标准库模块contextvars的文档中,指出:
上下文变量应该在顶层模块级别创建,而不是在闭包内创建。
但是,我不太清楚这句话的含义。虽然许多在线教程都提到了这一点,但没有一个提供解释。您能否提供一个在闭包中创建上下文变量的示例,并解释它可能导致的潜在问题?和GC有关系吗?
Context 对象持有对上下文变量的强引用,这会阻止上下文变量被正确的垃圾收集。
这意味着如果在函数中创建了上下文变量,则在调用结束后它将继续存在。每次调用该函数时,都会创建一个新的上下文变量(具有相同的名称)。
在短期内,运行一次,脚本没有问题 - 但将使用上下文变量的典型代码库是服务器或其他长时间运行的任务 - 这些对象的累积创建意味着内存泄漏。
参见 REPL 上的这些实验示例:
In [6]: import contextvars
In [7]: def blah():
...: x = contextvars.ContextVar("x")
...: x.set(0)
...:
In [8]: _ = [blah() for _ in range(10)]
In [9]: ctx = contextvars.copy_context()
In [10]: list(ctx.keys())
Out[10]:
[<ContextVar name='x' at 0x7f2c74557150>,
<ContextVar name='x' at 0x7f2c75ae7150>,
<ContextVar name='x' at 0x7f2c75153fb0>,
<ContextVar name='x' at 0x7f2c75153790>,
<ContextVar name='x' at 0x7f2c751537e0>,
<ContextVar name='x' at 0x7f2c75153e20>,
<ContextVar name='x' at 0x7f2c75153650>,
<ContextVar name='x' at 0x7f2c745562a0>,
<ContextVar name='x' at 0x7f2c75153ec0>,
<ContextVar name='x' at 0x7f2c75148cc0>]
如您所见,每次调用该函数时,当前上下文(在函数
blah
之外)都会保存一个 ContextVar
实例(及其各自的值),即使在函数结束后也是如此,并且在执行期间对局部变量x
内容的其他引用。
(即使 x
内没有为 blah
设置任何值,仍然会保留对 contextvar 对象本身的引用,即使它在函数作用域之外不可检索)
“顶层”仍然不是使用它们的唯一选择 - 重要的是上下文变量的创建发生在进程中的一个位置。我一直将它们创建为类属性,而不是直接在类的主体中创建:它确实感觉更惯用,特别是如果该类的方法将使用该变量的话。