gpt4 book ai didi

python - Python *with* 语句是否完全等同于 try - (except) - finally block ?

转载 作者:IT老高 更新时间:2023-10-28 20:53:31 25 4
gpt4 key购买 nike

我知道这已被广泛讨论,但我仍然找不到答案来确认这一点:with 语句是否与在 try - (except) -finally block 中调用相同的代码相同,上下文管理器的 __exit__ 函数中定义的任何内容都放在 finally block 中?

例如 -- 这 2 个代码片段是否在做完全相同的事情?

import sys
from contextlib import contextmanager

@contextmanager
def open_input(fpath):
fd = open(fpath) if fpath else sys.stdin
try:
yield fd
finally:
fd.close()

with open_input("/path/to/file"):
print "starting to read from file..."

同:

def open_input(fpath):
try:
fd = open(fpath) if fpath else sys.stdin
print "starting to read from file..."
finally:
fd.close()

open_input("/path/to/file")

谢谢!

最佳答案

我将暂不提及范围,因为它真的不是很相关。

根据PEP 343 ,

with EXPR as VAR:
BLOCK

翻译成

mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)

如您所见,type(mgr).__enter__ 被按预期调用,但不在 try 内。

type(mgr).__exit__ 在退出时被调用。唯一的区别是出现异常时,走if not exit(mgr, *sys.exc_info())路径。这使 with 能够自省(introspection)和消除错误,这与 finally 子句不同。


contextmanager 不会使这很多复杂化。只是:

def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, *args, **kwds)
return helper

然后看看有问题的类:

class _GeneratorContextManager(ContextDecorator):
def __init__(self, func, *args, **kwds):
self.gen = func(*args, **kwds)

def __enter__(self):
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None

def __exit__(self, type, value, traceback):
if type is None:
try:
next(self.gen)
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
if value is None:
value = type()
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration as exc:
return exc is not value
except:
if sys.exc_info()[1] is not value:
raise

不重要的代码已被省略。

首先要注意的是,如果有多个yield,这段代码会报错。

这不会明显影响控制流。

考虑 __enter__

try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None

如果上下文管理器写得很好,这将永远不会超出预期。

一个区别是,如果生成器抛出 StopIteration,则会产生不同的错误(RuntimeError)。这意味着行为不是如果您运行的是完全任意的代码,则与普通的 with 完全相同。

考虑一个不出错的__exit__:

if type is None:
try:
next(self.gen)
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")

唯一的区别是和以前一样;如果您的代码抛出 StopIteration,它将影响生成器,因此 contextmanager 装饰器会误解它。

这意味着:

from contextlib import contextmanager

@contextmanager
def with_cleanup(func):
try:
yield
finally:
func()

def good_cleanup():
print("cleaning")

with with_cleanup(good_cleanup):
print("doing")
1/0
#>>> doing
#>>> cleaning
#>>> Traceback (most recent call last):
#>>> File "", line 15, in <module>
#>>> ZeroDivisionError: division by zero

def bad_cleanup():
print("cleaning")
raise StopIteration

with with_cleanup(bad_cleanup):
print("doing")
1/0
#>>> doing
#>>> cleaning

这不太重要,但可能。

最后:

else:
if value is None:
value = type()
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration as exc:
return exc is not value
except:
if sys.exc_info()[1] is not value:
raise

这引发了关于 StopIteration 的相同问题,但有趣的是要注意最后一部分。

if sys.exc_info()[1] is not value:
raise

这意味着如果异常未处理,则回溯将保持不变。如果已处理但存在 new 回溯,则将改为引发该回溯。

这完全符合规范。


TL;DR

  • with 实际上比 try...finally 稍微强大一点,因为 with 可以内省(introspection)和消除错误。

  • 小心 StopIteration,否则你可以使用 @contextmanager 创建上下文管理器。

关于python - Python *with* 语句是否完全等同于 try - (except) - finally block ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26096435/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com