![](/img/trans.png)
[英]How to terminate a python process and enter the finally block in python try-except statement?
[英]Is Python *with* statement exactly equivalent to a try - (except) - finally block?
我知道這被廣泛討論,但我仍然找不到答案來證實這一點: 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..."
同樣如下:
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
內省和沉默錯誤的能力,這with
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
這意味着如果未處理異常,則回溯將保持不變。 如果它已被處理但存在新的回溯,則會引發該回溯。
這完全符合規范。
with
實際上要稍微強過一個try...finally
,該with
能反思和沉默的錯誤。
注意StopIteration
,但是你可以使用@contextmanager
來創建上下文管理器。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.