10

我不打算简单地浪费您的时间,但是:您是否也想过,在使用 Python 的with声明时,它确实与“Python 之禅”的第 5 行“平面优于嵌套”相反?任何开明的 Python 大师都可以与我分享他们对此的一些见解吗?

(我总是发现,每次我使用with而不是f.close()...时,我的代码中都会弹出一个多层次的缩进,这并不是说我try: ... finally: ...无论如何都不会使用,因此with即使我越来越喜欢,我仍然无法获得它的好处并且越来越了解 Python...)


@glglgl(对不起,我找不到在评论中写代码的方法):是的,但是如果你顺其自然with,你的代码就会变成:

try:
    with file(...) as f:
        ...
except IOError:
    ...

...并且只使用 with 而没有 thetry是人们最终在他们使用而不是 with 的那种骇人听闻的“一次性”代码中所做的事情f.close()(这很糟糕,因为如果在他们之前抛出异常,文件可能不会关闭f.close()),所以对于“hacky”代码,人们只是不使用with,因为,我不知道,我猜他们只是觉得它太“花哨”,而且对于结构良好的代码,它无论如何都不会带来任何好处,所以它似乎我没有留下任何真实世界的用例……那是我真正思考的问题。

4

5 回答 5

8

是的,Python 之禅说“扁平胜于嵌套”,但这并不是我们关心的唯一特性;它还指出“简单胜于复杂”。它的美妙之with处在于它实际上遵守了这两个原则,我将在下面解释。

每当您发现自己对 Python 中的某个功能进行哲学思考时,可能都值得查找Python 增强提案 (PEP)以了解该功能背后的动机。在这种情况下,PEP 343——“with”语句在摘要中预先说明:

此 PEP 向 Python 语言添加了一个新语句“with”,以便可以排除 try/finally 语句的标准用法。

分解try/finally语句使代码更简单,更具可读性。

然而, PEP 343比提供一些简单的语法糖更深入。它建立了一个上下文管理器协议:

语句中紧跟 with 关键字的表达式是“上下文表达式”,因为该表达式提供了上下文管理器在语句主体期间建立的运行时环境的主要线索。

使用上下文管理器协议,API 编写者可以帮助隐藏复杂性并确保在多线程上下文中正确获取/释放资源。

但是PEP 343with的示例 12 显示了该语句的真正优点,它解释了:

一个“嵌套”上下文管理器,它自动从左到右嵌套提供的上下文以避免过度缩进。

使用nested()上下文管理器,您可以获取如下所示的代码:

with a as x:
    with b as y:
        with c as z:
            # Perform operation

并将其变成这样:

with nested(a, b, c) as (x, y, z):
             # Perform operation

请注意,它nested()是在 Python 2.5 中引入的,但从 2.7 版开始,它已被弃用,而支持这种多上下文管理器语法形式:

with a as x, b as y, c as z:
             # Perform operation

显然,这不仅更简单、更易读,而且比嵌套更扁平。因此,使用with是遵循无为的路径:)

更新:为了回应对 Simeon Visser 的回答的评论,这里是一个示例,当您想将两个(或多个)文件的内容压缩在一起时,您可能会使用多个上下文管理器一次打开多个文件,这样如果打开其中一个文件失败,它将使整个事情失败并正确关闭每个打开的文件:

from itertools import izip
with open("/etc/passwd") as a, open("/etc/group") as b, open("/etc/shadow") as c:
    for lines in izip(a,b,c):
        print map(lambda x: x.split(':')[0], lines)

运行此示例两次;一次以root身份,一次以普通用户身份。假设您将此文件保存为ziptogether.py第一次尝试以 root 身份调用它sudo python ziptogether.py并且它会成功,但是作为普通用户调用它python ziptogether.py会失败,因为您没有读取权限/etc/shadow。当它失败时,上下文管理器将确保在执行移出with语句范围时正确关闭失败之前成功打开的文件。

于 2012-07-07T16:39:18.963 回答
7

您已经提到过:这样做更清洁

f = file(...)
try:
    # do work on file
finally:
    f.close()

不仅仅是在文件操作后关闭 - 如果发生异常,则不会到达。

如果你比较try/finallyto with,你有相同级别的缩进,所以你不会丢失任何东西。然而,如果你进行异常处理,你就会多一层缩进,这确实违背了禅宗的观点。

OTOH,with封装事物并使它们更容易和更易读,这是其他禅宗方面。

在我看来,要始终准确地遵循禅的每一个方面似乎是不可能的。有时你必须权衡一个对另一个。在这种情况下,您“失去”了一层缩进,但您获得了更好的可读性和可维护性。后者对我来说似乎是一个优势。

于 2012-07-07T08:34:11.700 回答
7

请注意,Python 之禅也说:

简单胜于复杂。

复杂胜于复杂。

可读性很重要。

with在语句中使用上下文管理器提供了多种功能:

  • 正确的行为,因为文件总是关闭
  • 可读性(with open(..) as f很好理解)

你不能指着 Python 之禅中的一个项目,并争论所有 Python 代码必须始终满足所有项目。例如,如果以可读和正确的方式解决特定问题的最小缩进级别是四,那么就这样吧:如果缩进级别为三使代码的可读性降低,那么就不要管代码了(四是好的)。

于 2012-07-07T09:12:41.480 回答
1
"Flat is better than nested"

那么,什么是平面

import thirdparty
print "I Like Pie!"

对比

import org.example.thirdparty.something
System.out.println("I Like Cake")

ETC...

Python 之禅并不仅仅对您的代码强制执行缩进限制。它鼓励您编写可读(因此更好)的代码。如果您的with语句位于只能由 3 层对象(等one.two.three.func())访问的函数中,那么这是一个问题。

否则,三个缩进级别与任何缩进级别一样好。

于 2012-07-07T10:50:36.737 回答
1

更喜欢 的原因with是您不需要手动配对相关操作(例如open(...)/ .close();但该with结构更通用——不仅用于处理文件)。这很重要,即在第二个操作可能由于源代码中不清晰可见的原因而无法执行的情况下。您是在告诉机器为我处理它,并且机器在这种情况下比人类更好。这样,您就可以摆脱可能难以找到的令人讨厌的错误组。

顺便说一句,您应该使用open(...)而不是file(...). Python 3 对此一无所知,file(...)否则您将不得不稍后修复您的代码。

于 2012-07-07T12:36:27.593 回答