该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?比兹利发表这样的声明是什么意思?
好的,假设我们有一个对象,我们称它为k
。 k
实现了一个“上下文管理器”,这意味着它具有方法函数k.__enter__()
和k.__exit__()
. 现在我们这样做:
with k as x:
# do something
大卫比兹利想让你知道的是,这x
不一定是必然的k
。 x
将被绑定到任何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
对象的一个成员中。以后如果我们需要别的东西,我们也可以把它塞进去。