理解Python’with’语句

Understanding the Python 'with' statement

我试着去理解这两者之间是否有区别,以及区别可能是什么。

选项一:

1
2
3
4
file_obj = open('test.txt', 'r')

with file_obj as in_file:
    print in_file.readlines()

选项二:

1
2
with open('test.txt', 'r') as in_file:
    print in_file.readlines()

我理解,对于选项1,file_obj在WITH块之后处于关闭状态。


我不知道为什么还没有人提到这一点,因为这是with工作方式的基础。与Python中的许多语言功能一样,后台的with调用特殊方法,这些方法已经为内置的Python对象定义,并且可以被用户定义的类覆盖。在with的特殊情况下(和更一般的上下文管理器),方法是__enter____exit__

记住,在Python中,一切都是一个对象——甚至是文本。这就是为什么你可以做像'hello'[0]这样的事情。因此,无论您是否直接使用open返回的文件对象:

1
2
3
with open('filename.txt') as infile:
    for line in infile:
        print(line)

或者先用不同的名称存储它(例如,分解一条长线):

1
2
3
4
the_file = open('filename' + some_var + '.txt')
with the_file as infile:
    for line in infile:
        print(line)

因为最终的结果是the_fileinfileopen的返回值都指向同一个对象,这就是with调用__enter____exit__方法的原因。内置文件对象的__exit__方法关闭文件。


它们的行为相同。作为一般规则,不能通过将表达式赋给同一范围内的变量来改变python代码的含义。

这与它们相同的原因相同:

1
f = open("myfile.txt")

VS

1
2
filename ="myfile.txt"
f = open(filename)

无论是否添加别名,代码的含义都保持不变。上下文管理器的含义比将参数传递给函数要深,但原理是相同的:上下文管理器的魔力应用于同一对象,并且在这两种情况下文件都会关闭。

选择一个而不是另一个的唯一原因是,如果您觉得它有助于代码的清晰性或风格。


如果您只是启动python并使用这些选项中的任何一个,那么如果不更改python的file对象的基实例,净效果是相同的。(在选项1中,只有当选项2中的file_obj超出范围vs在块末尾时,文件才会关闭,正如您已经观察到的那样。)

但是,与使用上下文管理器的用例可能存在差异。由于file是一个对象,您可以修改它或对它进行子类化。

您也可以通过调用file(file_name)打开一个文件,显示file与其他对象的行为类似(但没有人以python方式打开文件,除非它与with一起打开):

1
2
3
4
5
6
7
8
9
>>> f=open('a.txt')
>>> f
<open file 'a.txt', mode 'r' at 0x1064b5ae0>
>>> f.close()

>>> f=file('a.txt')
>>> f
<open file 'a.txt', mode 'r' at 0x1064b5b70>
>>> f.close()

更一般地说,打开和关闭名为the_thing的某些资源(通常是一个文件,但可以是任何内容)遵循以下步骤:

1
2
3
4
5
6
set up the_thing                       # resource specific, open, or call the obj
try                                    # generically __enter__
    yield pieces from the_thing
except
    react if the_thing is broken
finally, put the_thing away            # generically __exit__

您可以使用上下文管理器和在open和代码的其他元素之间编织的过程代码更容易地更改这些子元素的流。

从python 2.5开始,文件对象有enter和exit方法:

1
2
3
4
5
>>> f=open('a.txt')
>>> f.__enter__
<built-in method __enter__ of file object at 0x10f836780>
>>> f.__exit__
<built-in method __exit__ of file object at 0x10f836780>

默认的python file对象以这种方式使用这些方法:

1
2
3
4
5
__init__(...)            # performs initialization desired

__enter__() -> self      # in the case of `file()` return an open file handle

__exit__(*excinfo) -> None.  # in the case of `file()` closes the file.

这些方法可以更改以供您自己使用,以修改打开或关闭资源时如何处理该资源。上下文管理器使得修改打开或关闭文件时所发生的事情变得非常容易。

简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Myopen(object):
    def __init__(self, fn, opening='', closing='', mode='r', buffering=-1):
        # set up the_thing

        if opening:
            print(opening)
        self.closing=closing    
        self.f=open(fn, mode, buffering)

    def __enter__(self):
        # set up the_thing
        # could lock the resource here
        return self.f

    def __exit__(self, exc_type, exc_value, traceback):
        # put the_thing away
        # unlock, or whatever context applicable put away the_thing requires
        self.f.close()
        if self.closing:
            print(self.closing)

现在试试看:

1
2
3
4
5
6
>>> with Myopen('a.txt', opening='Hello', closing='Good Night') as f:
...     print f.read()
...
Hello
[contents of the file 'a.txt']
Good Night

一旦您控制了资源的进入和退出,就会出现许多用例:

  • 锁定资源以访问和使用它;完成后解锁
  • 使一个奇怪的资源(如内存文件、数据库或网页)更像一个直接的文件资源
  • 打开数据库,如果有异常则回滚,如果没有错误则提交所有写入
  • 临时更改浮点计算的上下文
  • 计时一段代码
  • 通过从"退出"方法返回TrueFalse,更改您提出的异常。
  • 您可以在PEP 343中阅读更多示例。


    这两种方法之间没有区别—当您退出WITH块时,关闭文件的方式也是一样的。

    您给出的第二个示例是在python 2.6和更新版本(添加with语法时)中使用文件的典型方式。

    您可以验证第一个示例是否也适用于这样的repl会话:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> file_obj = open('test.txt', 'r')
    >>> file_obj.closed
    False
    >>> with file_obj as in_file:
    ...     print in_file.readlines()
    <Output>
    >>> file_obj.closed
    True

    因此,在with块退出后,文件将关闭。

    不过,通常情况下,第二个例子是如何做这种事情。

    没有理由创建额外的变量file_obj……在with块结束后,您可能想做的任何事情都可以使用in_file,因为它仍然在范围内。

    1
    2
    >>> in_file
    <closed file 'test.txt', mode 'r' at 0x03DC5020>