Cla*_*ara 19 python with-statement contextmanager
我知道这已被广泛讨论,但我仍然找不到答案来证实这一点:with语句与在try - (除了)-finally块中调用相同代码相同,其中无论在__exit__函数中定义了什么上下文管理器放在finally块中?
例如 - 这两个代码片段完全相同吗?
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..."
Run Code Online (Sandbox Code Playgroud)
同样如下:
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")
Run Code Online (Sandbox Code Playgroud)
谢谢!
Vee*_*rac 27
我将暂时提到范围,因为它真的不太相关.
根据PEP 343,
with EXPR as VAR:
BLOCK
Run Code Online (Sandbox Code Playgroud)
翻译成
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)
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,type(mgr).__enter__正如你所期望的那样,而不是在try你的内心.
type(mgr).__exit__在退出时调用.唯一的区别是,当存在异常时,将采用该if not exit(mgr, *sys.exc_info())路径.with与finally子句可以做的不同,这使得能够内省和沉默错误.
contextmanager没有这个复杂得多.只是:
def contextmanager(func):
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func, *args, **kwds)
return helper
Run Code Online (Sandbox Code Playgroud)
然后看看有问题的课程:
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
Run Code Online (Sandbox Code Playgroud)
已经省略了不重要的代码.
首先要注意的是,如果有多个yields,则此代码将出错.
这不会明显影响控制流程.
考虑__enter__.
try:
return next(self.gen)
except StopIteration:
raise RuntimeError("generator didn't yield") from None
Run Code Online (Sandbox Code Playgroud)
如果上下文管理器写得很好,这将永远不会超出预期.
一个区别是,如果发生器抛出StopIteration,RuntimeError将产生不同的错误().这意味着with如果您运行完全任意的代码,行为与正常情况不完全相同.
考虑一个无错误__exit__:
if type is None:
try:
next(self.gen)
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
Run Code Online (Sandbox Code Playgroud)
唯一的区别是和以前一样; 如果您的代码抛出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
Run Code Online (Sandbox Code Playgroud)
哪个不太重要,但可能.
最后:
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
Run Code Online (Sandbox Code Playgroud)
这提出了同样的问题StopIteration,但有趣的是注意到最后一部分.
if sys.exc_info()[1] is not value:
raise
Run Code Online (Sandbox Code Playgroud)
这意味着如果未处理异常,则回溯将保持不变.如果它已被处理但存在新的回溯,则会引发该回溯.
这完全符合规范.
with实际上比稍微更强大try...finally的是,with可以反思和沉默的错误.
要小心StopIteration,否则你可以@contextmanager用来创建上下文管理器.
| 归档时间: |
|
| 查看次数: |
3509 次 |
| 最近记录: |