"with",上下文管理器,python:简单来说是怎么回事?

"with", context manager, python: What's going on in simple terms?

这里的 Python 编码新手来自 Java 背景。我仍然对此感到困惑:

with open(...) as f:
    do_something(f)

即使在谷歌搜索和阅读这里的一些答案之后(我还是无法理解它们)。

我的理解是,有一个叫做上下文管理器的东西,它是某种包装器,包含对创建的文件的引用。关于

as f:

上面的'as'和下面的'as'一样

import numpy as np

这只是一个别名。 'f' 不是指文件,而是上下文管理器。上下文管理器,使用装饰器模式,实现打开文件的所有方法,这样我就可以像对待文件对象一样对待它(并通过调用适当的方法获取文件对象,这些方法将在文件上调用在上下文管理器中)。而且,当然,当块完成时文件将关闭(这就是重点)。

这引出了一个问题:一般来说,open() return 是一个文件(或对文件的引用)还是上下文管理器?它是 return 一般的上下文管理器吗?这就是我们一直在使用但不自知的东西吗?或者它是否 return 文件类型,除了在这个特殊的上下文中,当 return 像上下文管理器一样不同时。

这里离右边近吗?有人想澄清一下吗?

文件对象本身就是上下文管理器,因为它们有__enter__ and __exit__方法。 with 在进入和退出上下文时通知 file 对象(分别通过调用 __enter____exit__),这就是文件对象 "knows" 的方式关闭文件。这里不涉及包装对象;文件对象提供了这两种方法(在 Java 术语中,您可以说文件对象实现了上下文管理器接口)。

请注意 as 而不是 import module as altname 一样的别名;相反,contextmanager.__enter__()return 值 被分配给目标。 fileobject.__enter__() 方法 returns self (所以文件对象本身),为了更容易使用语法:

with open(...) as fileobj:

如果 fileobject.__enter__() 没有这样做,而是 returned None 或另一个对象,则不能内联 open() 调用;要保留对 returned 文件对象的引用,您必须先将 open() 的结果分配给变量,然后再将其用作上下文管理器:

fileobj = open(...)
with fileobj as something_enter_returned:
    fileobj.write()

fileobj = open(...)
with fileobj:  # no as, ignore whatever fileobj.__enter__() produced 
    fileobj.write()

请注意,没有什么能阻止您在自己的代码中使用后一种模式;如果您已经有另一个对文件对象的引用,或者根本不需要进一步访问文件对象,那么您 没有 在此处使用 as target 部分。

但是,其他上下文管理器可以 return 不同的东西。一些数据库连接器 return 一个数据库游标:

conn = database.connect(....)
with conn as cursor:
    cursor.execute(...)

并且退出上下文会导致事务被提交或回滚(取决于是否存在异常)。

上下文管理器是相当简单的野兽......它们只是 类 定义 two separate methods (__enter__ and __exit__)。当执行 with 语句时,从 __enter__ 返回的任何内容都绑定在 with 语句的 as x 子句中。

这是一个非常愚蠢的例子:

>>> class CM(object):
...   def __enter__(self):
...     print('In __enter__')
...     return 'Hello world'
...   def __exit__(self, *args):
...     print('In __exit__')
... 
>>> with CM() as x:
...   print(x)
... 
In __enter__
Hello world
In __exit__

你会经常看到上下文管理器只是从 __enter__ 方法返回 self,但我写上面的例子是为了证明你没有 [=48] =] 到。另外注意在with语句中不需要构造context manager,可以提前构造:

cm = CM()
with cm as x:
    ...

上下文管理器的原因是当与with语句一起使用时,python保证__exit__将被调用(即使[=内部发生异常16=]套房)1.

file 对象是使用上下文管理器 API 实现的(它们具有明确定义的 __enter____exit__ 方法)所以 file 对象 个上下文管理器。当与 with 语句一起使用时,python 保证当 with 套件退出时,文件将被关闭。

1除非出现灾难性的系统故障——例如如果你的电脑爆炸了...

这是您可以创建的最基本的上下文管理器:

class UselessContextManager(object):
    def __enter__(self):
        pass

    def __exit__(self, type, value, traceback):
        pass

with UselessContextManager() as nothing:
    print(nothing is None)

如果您想稍微了解一下实际流程的样子,请试试这个:

class PrintingContextManager(object):                                          
    def __init__(self, *args, **kwargs):                                       
        print('Initializing with args: {} and kwargs: {}'.format(args, kwargs))

    def __enter__(self):                                                       
        print('I am entering the context')                                     
        print('I am returning 42')                                             
        return 42                                                              

    def __exit__(self, type, value, traceback):                                
        print('And now I am exiting')                                          


print('Creating manager')                                                      
manager = PrintingContextManager()                                             
print('Entering with block')                                                   
with manager as fnord:                                                         
    print('Fnord is {}'.format(fnord))                                         
    print('End of context')                                                    
print('Out of context')                                                        

输出:

Creating manager
Initializing with args: () and kwargs: {}
Entering with block
I am entering the context
I am returning 42
Fnord is 42
End of context
And now I am exiting
Out of context

您应该尝试修改代码以打印出 type, value, traceback,然后在 with 块内引发异常。

如您所见,with 语法 几乎 只是以下内容的缩写:

thing = ContextManager()
try:
    stuff = thing.__enter__()
except Exception as e:
    stuff.__exit__(type(e), e.args[0], e.__traceback__)

Though truthfully it's a bit different

您可以看到文件始终是上下文管理器:

>>> f = open('/tmp/spanish_inquisition.txt', 'w')
>>> f.__enter__
<function TextIOWrapper.__enter__>
>>> f.__exit__
<function TextIOWrapper.__exit__>

I didn't know a File could be a ContextManager simply by implementing two methods, without inheriting from a super class or explicitly implementing an interface. Again, I'm new to this language.

在Python中显式实现一个接口。在 Java 中,您必须指定要遵守的接口。在Python中,你就去做吧。需要一个类似文件的对象?添加一个 .read() 方法。也许 .seek().open().close() 取决于他们的期望。但是在 Python...

it = DecoyDuck()
if it.walks_like_a_duck() and it.talks_like_a_duck() and it.quacks_like_a_duck():
    print('It must be a duck')