6

Raymond Hettinger在展示幻灯片 36 和 37 时让不少人感到惊讶。语句可用于打开文件,但不能用于这些新事物。查看有关线程的 python 3.3 文档,仅在最底部的第 16.2.8 节中甚至提到了它。从讲座中可以看出,使用“with”运算符是最佳实践。

  • 应该如何确定是否支持'with',它可以绑定到什么等?
  • 另外,“with”应该怎么称呼?(threading with statement,python threading lock with statement,...),什么是白话来搜索并查看是否支持'with'(我们可以询问某些东西是否可迭代,我们是否询问它是否'withable')?

参考:

4

3 回答 3

7

首先,你不问某事是否“可使用”,而是问它是否是“上下文管理器”。*

例如,在您链接的文档中(顺便说一下,它们来自 3.1,而不是 3.3):

目前,LockRLockConditionSemaphoreBoundedSemaphore对象可以用作with语句上下文管理器。

同时,如果你想在交互式解释器中进行搜索,有两件显而易见的事情要做:

if hasattr(x, '__exit__'):
    print('x is a context manager')

try:
    with x:
        pass
except AttributeError:
    pass
else:
    print('x is a context manager')

同时:

help(open)……没有提及

嗯,是的,因为open它不是上下文管理器,它是一个碰巧返回上下文管理器的函数。在 3.3 中,它可以根据其参数返回各种不同的东西;在 2.7 中,它只返回一件事 (a file),但help会告诉您它返回的确切内容,然后您可以使用help适合您的用例的任何一个,或者只查看它的属性,看看它定义了__exit__.

无论如何,实际上,请记住 EAFTP 适用于调试和原型设计以及您的最终代码。尝试先写一些带有with声明的东西。如果您尝试用作上下文管理器的表达式不是一个,那么您将在尝试运行该代码时立即收到异常,这很容易调试。(这通常是AttributeError关于缺少的__exit__,但即使不是,回溯表明它来自您的with线路的事实应该告诉您问题。)如果您有一个看起来应该可用的对象作为上下文管理器,但不是,您可能需要考虑提交错误/将其提交到邮件列表/等。(在有人抱怨之前,stdlib 中有一些类不是上下文管理器。)

最后一件事:如果您使用的类型具有close方法,但不是上下文管理器,请contextlib.closing在它周围使用:

with closing(legacy_file_like_object):

… 或者

with closing(legacy_file_like_object_producer()) as f:

实际上,您应该真正查看contextlib. @contextmanager非常漂亮,nested如果您需要将 2.7/3.x 代码反向移植到 2.5,并且虽然closing编写起来很简单(如果您有@contextmanager),但使用 stdlib 函数可以使您的意图变得清晰。


* 实际上,关于命名存在一些争论,并且它经常在邮件列表中重复出现。但是文档和help('with')两者都给出了近乎精确的定义,“上下文管理器”是评估“上下文表达式”的结果。因此, inwith foo(bar) as baz, qux as quux:foo(bar)都是qux上下文管理器。(或者也许在某种程度上,他们两个组成了一个上下文管理器。)

于 2013-04-04T19:48:38.523 回答
3

afaik 实现__exit__方法的任何类/对象(您可能还需要实现__enter__

>>>dir(file)
#notice it includes __enter__ and __exit__

所以

def supportsWith(some_ob):
   if "__exit__" in dir(some_ob): #could justas easily used hasattr
       return True
于 2013-04-04T19:44:36.470 回答
2

使用 Pythonwith语句的对象称为上下文管理器。在典型的 Python 风格中,一个对象是否是一个上下文管理器只取决于你是否可以用它做“上下文管理器”的事情。(这种策略称为鸭式打字。)

那么什么构成“上下文管理器-y”行为?确实有两件事:(1)在进入街区时进行一些标准设置with,以及(2)在退出街区之前进行一些标准的“拆除”,如果出现问题,可能还会进行一些损坏控制。就是这样。

详细信息在介绍该声明的PEP 343with以及您在问题中链接的文档中提供。

一个with块,一步一步

但是,让我们一步一步地完成这个过程。

首先,我们需要一个“上下文管理器”。这是任何提供设置和拆卸行为的对象,封装在分别称为__enter__和的方法中__exit__。如果一个对象提供了这些方法,它就有资格作为一个上下文管理器,尽管如果这些方法没有做明智的事情,它可能是一个糟糕的管理器。

with那么当解释器看到一个块时,幕后会发生什么?首先,解释器在语句之后提供的对象上查找__enter____exit__方法。with如果方法不存在,那么我们没有上下文管理器,因此解释器会抛出异常。

但如果方法确实存在,一切都很好。我们有我们的上下文管理器,所以我们进入块。解释器然后执行上下文管理器的 __enter__方法并将结果分配给as语句后面的变量(如果有,否则结果被丢弃)。接下来,with执行块的主体。完成后,上下文管理器的__exit__语句将传递一个(可能为空的)字典,其中包含有关执行 bock 主体时发生的任何异常的信息,并执行该__exit__方法以清理内容。

以下是逐行演练:

with man as x:         # 1) Look for `man.__enter__` and `man.__exit__`, then ...
                       # 2) Execute `x = man.__enter__()`, then ...
    do_something(x)    # 3) Execute the code in the body of the block, ...
                       # 4) If something blows up, note it (in `err`), ...
                       # 5) Last, this (always!) happens: `man.__exit__(**err)`.
carry_on()

就是这样。

重点是什么?

这里唯一的微妙之处是上下文管理器的__exit__方法总是被执行,即使在with块的主体中​​抛出了未捕获的异常。在这种情况下,异常信息被传递给__exit__方法(在我在上面的示例中调用的字典err中),该方法应该使用该信息来提供损害控制。

因此,with块只是一种抽象,它让我们将设置、拆卸和损坏控制代码(即“上下文管理”)分流到几个方法中,然后可以在幕后调用这些方法。这既鼓励我们排除样板文件,又提供更简洁、易读的代码,突出核心控制流并隐藏实现细节。

于 2013-04-04T20:43:17.407 回答