【问题标题】:How "with" is better than try/catch to open a file in Python?“with”如何比 try/catch 在 Python 中打开文件更好?
【发布时间】:2012-02-05 04:27:21
【问题描述】:

我知道with 语句可以帮助您解决这个问题:

try:
    f = open(my_file)
    do_stuff_that_fails()
except:
    pass
finally:
    f.close()

进入:

with open(my_file) as f:
    do_stuff_that_fails()

但是这样更好吗?您仍然必须处理无法打开文件的情况(例如提示用户告诉他他没有权限),因此实际上您将拥有:

try:
    with open(my_file) as f:
        do_stuff_that_fails()
except (IOError, OSError, Failure) as e:
    do_stuff_when_it_doesnt_work()

相当于:

try:
    f = open(my_file)
    do_stuff_that_fails()
except (IOError, OSError, Faillure) as e:
    do_stuff_when_it_doesnt_work()
finally:
    f.close()

是的,您获得了两行代码,但是您添加了一层嵌套,这并不能使其更易于阅读。 with 语句的目的是为您节省两行代码还是我遗漏了什么?

为此添加关键字似乎很多,所以我觉得有一些语法可以处理我不知道的额外尝试/除外。

【问题讨论】:

  • "with" 将是一个糟糕的标签名称,容易被新手滥用。我们能找到更好的吗?
  • Python 需要一个:trywith open('whatever', 'r') as f: .... except(blah): ....

标签: python exception with-statement


【解决方案1】:

首先,它有助于防止您在 try ... finally ... 示例中引入的问题。

按照您的结构方式,如果在尝试打开文件时抛出异常,那么您将永远不会将打开的文件绑定到名称 f,从而导致 finally 子句中的 NameError (如果 f 从未被绑定在范围内)或完全出乎意料的事情(如果有)。

正确的结构(相当于with)是:

f = open(my_file)

try:
    do_stuff_that_fails()
finally:
    f.close()

(注意 - 如果您在那里无事可做,则不需要 except 子句)。

你的第二个例子同样是错误的,应该是这样的结构:

try:
    f = open(my_file)

    try:
        do_stuff_that_fails()
    except EXPECTED_EXCEPTION_TYPES as e:
        do_stuff_when_it_doesnt_work()
    finally:
        f.close()

except (IOError, OSError) as e:
    do_other_stuff_when_it_we_have_file_IO_problems()

第二个是(如另一个答案所述)您不能忘记致电f.close()

顺便说一句,这个术语是“上下文管理”,而不是“资源管理”——with 语句管理 上下文,其中一些可能是资源,但其他不是。例如,它还与decimal 一起使用,为特定代码块建立十进制上下文。

最后(回应您对上一个答案的评论)您永远不应该依赖 refcount 语义来处理 Python 中的资源。 Jython、IronPython 和 PyPy 都具有非引用计数语义,并且没有什么能阻止 CPython 走另一条路(尽管在不久的将来不太可能)。在紧密循环中(例如os.walk),如果依赖于引用计数语义的代码在具有不同行为的 VM 上运行,则很容易用完文件句柄。

【讨论】:

  • +1 我什至没有注意到我犯了这么愚蠢的错误。感谢您指出这一点。
【解决方案2】:

在您给出的示例中,它不是更好。最佳做法是在接近引发异常的位置捕获异常,以避免捕获相同类型的不相关异常。

try:
    file = open(...)
except OpenErrors...:
    # handle open exceptions
else:
    try:
        # do stuff with file
    finally:
        file.close()

不幸的是,尽管如此冗长,with statement 不允许您捕获在其评估期间引发的异常。有一个suggestion 在邮件列表中添加了对此效果的异常处理:

with open(...) as file:
    # do stuff with file
except OpenErrors...:
    # handle open exceptions

但这是shot down

最后值得注意的是,您可以像这样直接进入和退出上下文管理器:

file = open(...).__enter__()
file.__exit__(typ, val, tb)

这在herehere有更详细的描述。

作为一般准则,with 语句适用于不会出现异常的情况,默认的“输入/打开/获取”行为就足够了。示例包括必需的文件和简单的锁定。

【讨论】:

    【解决方案3】:

    这是为了资源管理 ...而不是你对异常的反应:)

    使用with 时,无法“忘记”f.close()。通过这种方式,它的作用与 C# 中的 using 相同。

    编码愉快。

    【讨论】:

    • 所以真正做正确的事情而不必编写它。我喜欢资源管理这个词,它是有道理的。但是创建一个新的关键字和一种新的思考基本操作的方式来为您节省finally 感觉有点过头了,因为资源管理是在垃圾收集时调用的析构函数中自动处理的。除非您编写巨大的块或大量递归代码,否则很快就会发生在函数或方法的末尾。我知道你应该明确地关闭它们,但如果你不这样做,它会起作用。最终,有多少用例真正受益于with
    • @e-satis 析构函数不一定在所有实现中都是确定性的,即使 CPython 在历史上一直基于引用计数(但这仍然会被变量范围所困扰, with 现在处理)。我不确定为什么选择语法,但我喜欢这个功能。也许额外的trywith(...): 结构会很整洁。
    • 对,这是关于pythonic。你做了正确的事情,它是显而易见的、自动的、更短的和确定性的。只是让事情变得更容易一些,比如只是语法糖的装饰器。仅针对该评论,我将接受答案。
    • 我喜欢这个主意:mail.python.org/pipermail/python-ideas/2009-May/004903.html with...except 但是它被guido拒绝了。不过,他的回答是有道理的:您希望以非常清晰的方式明确处理异常。但是 try/except 在如此优雅的环境中是如此丑陋:-)
    • 尽管我很喜欢你的回答,但我认为@tim 值得被接受。
    猜你喜欢
    • 2015-03-27
    • 2011-03-14
    • 2012-11-25
    • 1970-01-01
    • 2014-12-18
    • 2014-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多