什么是“运行时上下文”?

时间:2013-10-29 07:59:57

标签: c++ python c

(编辑更清晰)

我正在阅读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做出这样的陈述是什么意思?

2 个答案:

答案 0 :(得分:8)

PEP 343中引入了with声明。该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中进行多线程编码,但是当我这样做时,这句话确保锁定总是被释放,即使面对异常也是如此。

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做出类似的陈述是什么意思?

好的,假设我们有一个对象,我们称之为kk实现了一个"上下文管理器",这意味着它具有方法函数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

此处,dc完全不同:d是与数据库的连接,c是用于一个事务的游标。

with子句由对__exit__()的调用终止,该调用被赋予该子句的执行状态 - 成功或失败。在这种情况下,__exit__()方法可以采取适当的行动。

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

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

上下文管理器用于简单地初始化和清理诸如文件,数据库等之类的东西。

我所知道的C或C ++没有直接的对应关系。

C不知道异常的概念,因此没有一个可以在__exit__()中捕获。 C ++知道异常,并且似乎有办法做soo(请看下面的评论)。