“with”语句没有使用“as”关键字的含义

55

我熟悉使用Python的with语句,以确保在抛出异常时对象得到正确地清理。通常这看起来像:

with file.open('myfile.txt') as f:
    do stuff...

这是简写,意为

f = file.open('myfile.txt'):
try:
    do stuff...
finally:
    f.close()

或者是其他类可能呈现的最终化程序。

我最近发现了一段处理OpenGL的代码,其中出现了以下内容:

with self.shader:
    (Many OpenGL commands)
请确认以下两种情况的区别: 1. 没有使用任何as关键字。这是否意味着类的__enter____exit__方法仍然会被调用,但对象从未在块中显式使用(即,它通过全局或隐式引用工作)? 2. 还是有其他含义逃脱了我的理解?

2
如果您不需要在with块内部别名上下文管理器,那么没关系 - 例如contextlib.suppress。严格来说,您可以执行with open(...): ...,但是由于您无法访问文件处理程序,因此没有太多意义! - jonrsharpe
2个回答

58

上下文管理器可以选择性地返回一个对象,该对象将被分配给由as命名的标识符。 被分配给as的是__enter__方法返回的对象,而不一定是上下文管理器本身。

使用as <identifier>有助于创建像open()调用这样的新对象,但并非所有的上下文管理器都是为上下文而创建的。 它们可以重复使用,已经被创建,例如数据库连接。

数据库连接为例。您只需创建一次数据库连接,但许多数据库适配器允许您将连接用作上下文管理器; 进入上下文时会启动事务,退出时便会提交(成功)或回滚(当有异常时):

with db_connection:
    # do something to the database

这里不需要创建新的对象,在使用db_connection.__enter__()进入上下文,然后再使用db_connection.__exit__()退出上下文,但我们已经连接对象的引用。

现在,如果连接对象在进入时生成游标对象,则将该游标对象分配到本地名称中是有意义的:

with db_connection as cursor:
    # use cursor to make changes to the database

db_connection在这里还没有被调用,它在此之前已经存在,我们已经有它的引用。但是无论 db_connection.__enter__() 产生了什么,现在都被赋值给了 cursor,并可以从那里继续使用。

文件对象也是这样处理的;open()返回一个文件对象,fileobject.__enter__()返回文件对象本身,所以你可以在一个步骤中使用 with 语句调用 open() 并分配一个新创建对象的引用,而不是两个步骤。如果没有那个小技巧,你将需要使用:

f = open('myfile.txt')
with f:
    # use `f` in the block

将所有这些应用于您的着色器示例; 您已经引用了self.shader。 很可能self.shader.__enter__()返回对self.shader的引用,但是既然您已经拥有完全可用的引用,为什么要为此创建新的本地引用?


2

上述答案表述得很好。

当我阅读时,唯一一件让我一直在问自己的事情是,以下场景的确认在哪里。如果在with语句的上下文中有一个赋值任务,那么赋值任务右侧的任何内容都会首先“绑定”到上下文中。因此,在以下示例中:

with db_connection():
   result = select(...)

我把这个放在这里,为了像我一样经常切换语言的人快速记住如何阅读和跟踪这里的引用。在IT技术中,select表示选择操作,而ref_to_connection.select(...)是对连接进行选择操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接