如何判断上下文管理器是可重用还是可重入?

时间:2014-07-30 19:31:17

标签: python

Python contextlib文档指出,上下文管理器可以是单一用途,可重用或可重入的。可重入的可以在多个with语句中使用,包括嵌套的语句;可重用但不可重入的可以在多个with语句中使用但不能嵌套。提到了几个例子。

https://docs.python.org/3/library/contextlib.html#reentrant-context-managers

其他情境管理人员的文档并不总是提及它们的内容。例如,patchunittest.mock上下文管理员的文档根本没有提到这一点。

一般情况下,您会在源代码中查看内容管理器是单用,可重用还是可重入?

1 个答案:

答案 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.Lockthreading.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__,然后在逻辑上确定重用/重新进入该上下文的含义是什么。