12

(为更清晰而编辑)

我正在阅读 Python 书(Beazley 的 Python Essential Reference),他说:

with语句允许一系列语句在 运行时上下文中执行,该运行时上下文由用作上下文管理器的对象控制。

这是一个例子:

with open("debuglog","a") as f:
    f.write("Debugging\n")
    statements
    f.write("Done\n")

他接着说:

with obj 语句接受可选的作为 var 说明符。如果给定,则 obj._ enter _() 返回的值被放入 var。需要强调的是,obj 不一定是分配给 var 的值。

我了解“with”关键字的作用机制:open返回一个文件对象,并且该对象可通过块主体内的f访问。我也明白会调用enter ()并最终exit () 。

但究竟什么是运行时上下文?一些低级细节会很好 - 或者,C中的一个例子。有人可以澄清“上下文”到底是什么以及它与其他语言(C,C++)的关系。我对上下文的理解是环境,例如:Bash shell在所有(显示的env)shell 变量的上下文中执行ls 。使用with关键字 - 是的f可以访问块的主体,但这不只是范围吗?例如:对于 x in y:这里x不在块内范围内并在块外保留它的值 - 这就是 Beazley 在谈到“运行时上下文”时的意思,即f仅在块内限定范围,而在 with-block 之外失去所有意义??为什么他说这些语句“运行时上下文中执行”???这像“评估”吗?

我知道这会open返回一个“未分配给 var”的对象?? 为什么不分配给var?比兹利发表这样的声明是什么意思?

4

2 回答 2

10

with声明是在 PEP 343 中引入的。该 PEP 还引入了一个新术语“上下文管理器”,并定义了该术语的含义。

简而言之,“上下文管理器”是一个具有特殊方法功能.__enter__().__exit__(). 该with语句保证.__enter__()会调用该方法来设置该with语句下缩进的代码块,并且还保证.__exit__()在退出该代码块时调用该方法函数(无论该块如何退出;例如,如果代码引发异常,.__exit__()仍然会被调用)。

http://www.python.org/dev/peps/pep-0343/

http://docs.python.org/2/reference/datamodel.html?highlight=context%20manager#with-statement-context-managers

with语句现在是处理任何具有明确设置和拆卸的任务的首选方式。使用文件,例如:

with open(file_name) as f:
    # do something with file

您知道完成后文件将正确关闭。

另一个很好的例子是资源锁:

with acquire_lock(my_lock):
    # do something

你知道在你获得锁之前代码不会运行,一旦代码完成,锁就会被释放。我不经常在 Python 中进行多线程编码,但是当我这样做时,该语句确保始终释放锁,即使面对异常也是如此。

PS 我在 Google 网上搜索了上下文管理器的示例,我发现了一个非常棒的:一个在特定目录中执行 Python 块的上下文管理器。

http://ralsina.me/weblog/posts/BB963.html

编辑:

运行时上下文是由对 的调用设置并由对 的调用.__enter__()拆除的环境.__exit__()。在我获取锁的示例中,代码块在有可用锁的上下文中运行。在读取文件的示例中,代码块在打开的文件的上下文中运行。

Python 内部对此没有任何秘密魔法。解析器没有特殊的作用域,没有内部堆栈,也没有什么特别的。您只需编写两个方法函数,.__enter__()Python在您的语句.__exit__()的特定点调用它们。with

再看一下 PEP 的这一部分:

请记住,PEP 310 大致提出了这种语法(“VAR =”部分是可选的):

    with VAR = EXPR:
        BLOCK

大致翻译为:

    VAR = EXPR
    VAR.__enter__()
    try:
        BLOCK
    finally:
        VAR.__exit__()

在这两个示例中,BLOCK都是在特定运行时上下文中运行的代码块,该运行时上下文由对 的调用设置VAR.__enter__()并由VAR.__exit__().

with该声明及其设置方式有两个主要好处。

更具体的好处是它是“语法糖”。我宁愿写一个两行with语句而不是六行语句序列;两个更容易写一个较短的,它看起来更好,更容易理解,而且更容易做对。六行对两行意味着更多的机会把事情搞砸。(在with声明之前,我通常对将文件 I/O 包装在一个try块中很草率;我只是偶尔这样做。现在我总是使用with并且总是得到异常处理。)

更抽象的好处是,这为我们提供了一种思考设计程序的新方法。Raymond Hettinger 在 PyCon 2013 的一次演讲中是这样说的:当我们编写程序时,我们会寻找可以分解成函数的通用部分。如果我们有这样的代码:

A
B
C
D
E

F
B
C
D
G

我们可以很容易地制作一个函数:

def BCD():
    B
    C
    D

A
BCD()
E

F
BCD()
G

但是我们从来没有真正干净的方法来设置/拆卸。当我们有很多这样的代码时:

A
BCD()
E

A
XYZ()
E

A
PDQ()
E

现在我们可以定义一个上下文管理器并重写上面的内容:

with contextA:
    BCD()

with contextA:
    XYZ()

with contextA:
    PDQ()

所以现在我们可以考虑我们的程序并寻找可以抽象为“上下文管理器”的设置/拆卸。Raymond Hettinger 展示了他发明的几个新的“上下文管理器”食谱(我绞尽脑汁想为你记住一两个例子)。

编辑:好的,我只记得一个。Raymond Hettinger 展示了一个将内置到 Python 3.4 中的方法,用于使用with语句忽略块中的异常。在这里看到它:https ://stackoverflow.com/a/15566001/166949

PS 我已经尽我所能来理解他在说什么......如果我犯了任何错误或错误陈述了什么,这在我身上,而不是在他身上。(而且他有时会在 StackOverflow 上发帖,所以如果我搞砸了,他可能会看到这个并纠正我。)

编辑:您已经用更多文字更新了问题。我也会具体回答。

这就是 Beazley 在谈到“运行时上下文”时的意思吗,即 f 仅在块内限定范围,而在 with-块之外失去所有意义?为什么他说这些语句“在运行时上下文中执行”???这像“评估”吗?

实际上,f不仅限于块内。当您as在语句中使用关键字绑定名称时with,该名称在块之后保持绑定。

“运行时上下文”是一个非正式的概念,它的意思是“由方法函数调用建立并由.__enter__()方法函数调用拆除的状态.__exit__()”。同样,我认为最好的例子是在代码运行之前获得锁。代码块在拥有锁的“上下文”中运行。

我知道 open 返回一个“未分配给 var”的对象??为什么不分配给var?比兹利发表这样的声明是什么意思?

好的,假设我们有一个对象,我们称它为kk实现了一个“上下文管理器”,这意味着它具有方法函数k.__enter__()k.__exit__(). 现在我们这样做:

with k as x:
    # do something

大卫比兹利想让你知道的是,这x不一定是必然的kx将被绑定到任何k.__enter__()回报。 k.__enter__()可以自由地返回对k自身的引用,但也可以自由地返回其他东西。在这种情况下:

with open(some_file) as f:
    # do something

调用open()返回一个打开的文件对象,它作为一个上下文管理器,它的.__enter__()方法函数实际上只是返回一个对自身的引用。

我认为大多数上下文管理器都会返回对 self 的引用。由于它是一个对象,它可以有任意数量的成员变量,因此它可以以方便的方式返回任意数量的值。但这不是必需的。

例如,可能有一个上下文管理器,它启动在.__enter__()函数中运行的守护进程,并从函数返回守护进程的进程 ID 号.__enter__()。然后该.__exit__()函数将关闭守护程序。用法:

with start_daemon("parrot") as pid:
    print("Parrot daemon running as PID {}".format(pid))
    daemon = lookup_daemon_by_pid(pid)
    daemon.send_message("test")

但是您也可以返回上下文管理器对象本身,其中包含您需要的任何值:

with start_daemon("parrot") as daemon:
    print("Parrot daemon running as PID {}".format(daemon.pid))
    daemon.send_message("test")

如果我们需要守护进程的 PID,我们可以把它放在.pid对象的一个​​成员中。以后如果我们需要别的东西,我们也可以把它塞进去。

于 2013-10-29T08:10:07.043 回答
3

with 上下文负责在进入时__enter__调用方法并将给定var设置为__enter__返回的任何内容。

在大多数情况下,这是以前处理过的对象——在文件的情况下,它是——但例如在数据库上,不是连接对象,而是返回一个游标对象。

文件示例可以这样扩展:

f1 = open("debuglog","a")
with f1 as f2:
    print f1 is f2

这将打印True在这里,文件对象由返回__enter__。(从它的角度来看,self。)

数据库的工作方式类似于

d = connect(...)
with d as c:
    print d is c # False
    print d, c

在这里,dc完全不同的是:d是连接到数据库,c是用于一个事务的游标。

with子句通过调用终止,该调用__exit__()被赋予该子句的执行状态 - 成功或失败。在这种情况下,该__exit__()方法可以适当地起作用。

在文件示例中,无论是否有错误,文件都会关闭。

在数据库示例中,通常事务在成功时提交并在失败时回滚。

上下文管理器用于轻松初始化和清理这些东西——文件、数据库等。

我知道在 C 或 C++ 中没有直接对应关系。

C 不知道异常的概念,因此不能在 a 中捕获任何异常__exit__()。C++ 知道异常,并且似乎有办法做到这一点(请看下面的评论)。

于 2013-10-29T08:16:12.883 回答