Python contextlib
文档指出,上下文管理器可以是单一用途,可重用或可重入的。可重入的可以在多个with
语句中使用,包括嵌套的语句;可重用但不可重入的可以在多个with
语句中使用但不能嵌套。提到了几个例子。
https://docs.python.org/3/library/contextlib.html#reentrant-context-managers
其他情境管理人员的文档并不总是提及它们的内容。例如,patch
中unittest.mock
上下文管理员的文档根本没有提到这一点。
一般情况下,您会在源代码中查看内容管理器是单用,可重用还是可重入?
答案 0 :(得分:9)
一个好方法是查看正在返回/修改的对象或由__enter__
调用设置的上下文,然后查看__exit__
调用中该对象/上下文发生的情况。通常了解状态在每个状态中是如何实际改变的,这将使得嵌套或重用该对象时会发生什么变得明显。
例如,当您with open("somefile") as f:
时,您将获得文件句柄。在__exit__
中,您正在关闭该文件句柄。当然,一旦文件句柄对象关闭就重新打开它是没有意义的,打开已经打开的文件句柄也没有意义。当然,关闭内部文件句柄也会关闭外部文件句柄,这将是有问题的。这就是没有人这样做的原因:
f = open("file.txt")
with f:
# stuff
# File will get closed here
关闭后使用f
毫无意义,所以我们总是使用:
with open("file.txt") as f:
threading.Lock
和threading.RLock
个对象也可以用作上下文管理器。这样做是有道理的:
l = threading.Lock()
with l: # This acquires the lock
# stuff
# Lock got released
with l: # Acquired again
# more stuff
# Released again
这不是,因为Lock
如果你试图递归地将会死锁
l = threading.Lock()
with l:
# stuff
with l: # Uh-oh, we tried acquiring an already acquired Lock. We'll deadlock here.
但这可以与RLock()
一起使用,可以递归获取。
stdlib的另一个例子:multiprocessing.Pool()
可以用作Python 3.3+中的上下文管理器。以下是文档的说法:
池对象现在支持上下文管理器协议。
__enter__()
返回池对象,__exit__()
调用terminate()
。
terminate()
说这样做:
立即停止工作进程而不完成未完成的工作。当池对象被垃圾收集时 将立即调用terminate()。
显然,这是一次性使用。
patch
上下文管理器正在临时修补某个对象,然后在修补完成后撤消它。嵌套肯定没有意义 - 你为什么要重新修补已修补的东西?但是,修补,取消修补,然后重新打补丁在逻辑上确实有意义,所以它应该是可重用的(测试显示就是这种情况)。
我认为没有任何明确定义的事情你可以说“寻找这个,你会知道一个上下文管理器是可重用的/可重入的/一次性的”,因为上下文管理器可以字面上做任何。您可以做的最好的事情是了解在__enter__
中建立的上下文,它是如何被分解的__exit__
,然后在逻辑上确定重用/重新进入该上下文的含义是什么。