(编辑更清晰)
我正在阅读Python书籍(Beazley的 Python Essential Reference ),他说:
with
语句允许在一个内部执行一系列语句 运行时上下文,由充当上下文管理器的对象控制。以下是一个例子:
with open("debuglog","a") as f: f.write("Debugging\n") statements f.write("Done\n")
他接着说:
with obj语句接受一个可选的as var说明符。如果给定,则为该值 由obj._ 返回 _()返回到var。重要的是要强调 obj不一定是分配给var。
的值
我理解'与'关键字的作用机制:文件对象由打开返回,该对象可通过 f 强烈的>在块体内。我也了解 输入(),最终将调用 退出()。
但是什么是运行时上下文呢?一些低级别的细节会很好 - 或者说是C中的一个例子。有人可以澄清一下“上下文”到底是什么以及它与其他语言(C,C ++)的关系。我对上下文的理解是环境,例如: Bash shell在所有( env 显示的)shell变量的上下文中执行 ls 。 使用带有关键字的 - 是 f 可以访问块体,但不仅仅是范围界定吗? 例如:for x in y:这里 x 不在块内,并且在块外保留它的值 - 这就是Beazley在讨论“运行时上下文”时的意思, f 仅限于块内并且在with-block之外失去所有重要性?为什么他说语句“在运行时上下文中执行 ”???这是“eval”吗?
我理解open
会返回一个“not ...赋值给var的对象
为什么不将它分配给var? Beazley做出这样的陈述是什么意思?
答案 0 :(得分:8)
PEP 343中引入了with
声明。该PEP还引入了一个新术语"上下文管理器",并定义了该术语的含义。
简单地说,一个"上下文管理器"是一个具有特殊方法函数.__enter__()
和.__exit__()
的对象。 with
语句保证调用.__enter__()
方法来设置在with
语句下缩进的代码块,并保证.__exit__()
方法函数将是在退出代码块时调用(无论块如何退出;例如,如果代码引发异常,仍将调用.__exit__()
)。
http://www.python.org/dev/peps/pep-0343/
with
语句现在是处理任何具有明确定义的设置和拆卸的任务的首选方法。使用文件,例如:
with open(file_name) as f:
# do something with file
您知道完成后文件将正确关闭。
另一个很好的例子是资源锁:
with acquire_lock(my_lock):
# do something
您知道代码在获得锁定之前不会运行,并且一旦代码完成,锁定将被释放。我不经常在Python中进行多线程编码,但是当我这样做时,这句话确保锁定总是被释放,即使面对异常也是如此。
P.S。我在网上搜索了一些关于上下文管理器的例子,我找到了一个很好的例子:一个在特定目录中执行Python块的上下文管理器。
http://ralsina.me/weblog/posts/BB963.html
编辑:
运行时上下文是通过调用.__enter__()
设置的环境,并通过调用.__exit__()
拆除。在我获取锁的示例中,代码块在具有锁可用的上下文中运行。在读取文件的示例中,代码块在正在打开的文件的上下文中运行。
Python内部没有任何秘密魔法可供选择。没有特殊的作用域,没有内部堆栈,并且解析器没有什么特别之处。您只需编写两个方法函数.__enter__()
和.__exit__()
,Python就会在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
语句之前,我通常对于在try
块中包装文件I / O很邋;;我有时只做它。现在我总是使用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
P.S。我尽我所能去理解他所说的话......如果我犯了任何错误或误报了什么,那就是我,而不是他。 (他有时会在StackOverflow上发帖,所以如果我搞砸了任何东西,他可能会看到这个并纠正我。)
编辑:您已用更多文字更新了问题。我也会特别回答。
这就是Beazley在谈到运行时上下文时的意思,f只是在块内限定并且在with-block之外失去了所有意义?为什么他说语句"在运行时上下文中执行" ???这是一个" eval" ??
实际上,f
不仅限于块内。使用as
语句中的with
关键字绑定名称时,名称将在块之后保持绑定。
"运行时上下文"是一个非正式的概念,它意味着"由.__enter__()
方法函数调用建立的状态,并由.__exit__()
方法函数调用拆除。"同样,我认为最好的例子是关于在代码运行之前获取锁定的例子。代码块在" context"中运行。有锁。
我知道open会返回一个对象,而不是......分配给var" ??为什么不分配给var? Beazley做出类似的陈述是什么意思?
好的,假设我们有一个对象,我们称之为k
。 k
实现了一个"上下文管理器",这意味着它具有方法函数k.__enter__()
和k.__exit__()
。现在我们这样做:
with k as x:
# do something
David Beazley希望您了解的是x
不一定会受到k
的约束。 x
将绑定到任何k.__enter__()
返回。 k.__enter__()
可以自由返回对k
本身的引用,但也可以自由返回其他内容。在这种情况下:
with open(some_file) as f:
# do something
对open()
的调用返回一个打开的文件对象,它作为一个上下文管理器,它的.__enter__()
方法函数确实只返回对它自己的引用。
我认为大多数上下文管理器都会返回对self的引用。由于它是一个对象,因此它可以包含任意数量的成员变量,因此它可以方便地返回任意数量的值。但这并不是必需的。
例如,可能有一个上下文管理器启动在.__enter__()
函数中运行的守护程序,并从.__enter__()
函数返回守护程序的进程ID号。然后.__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
成员中。后来如果我们需要其他东西,我们也可以把它藏在那里。
答案 1 :(得分: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
此处,d
和c
完全不同:d
是与数据库的连接,c
是用于一个事务的游标。
with
子句由对__exit__()
的调用终止,该调用被赋予该子句的执行状态 - 成功或失败。在这种情况下,__exit__()
方法可以采取适当的行动。
在文件示例中,无论是否有错误,文件都会关闭。
在数据库示例中,通常事务在成功时提交并在失败时回滚。
上下文管理器用于简单地初始化和清理诸如文件,数据库等之类的东西。
我所知道的C或C ++没有直接的对应关系。
C不知道异常的概念,因此没有一个可以在__exit__()
中捕获。 C ++知道异常,并且似乎有办法做soo(请看下面的评论)。