Ще оставя настрана споменаванията за обхват, защото наистина не е много уместно.
Според 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
способността да интроспектира и заглушава грешките, за разлика от това, което може да направи клауза 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
s, този код ще доведе до грешка.
Това не се отразява забележимо на контролния поток.
Помислете за __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
Това означава, че ако изключението не е обработено, обратното проследяване ще бъде непроменено. Ако е бил обработен, но съществува нова обратна връзка, тя ще бъде повдигната вместо това.
Това напълно съответства на спецификацията.
TL;DR
with
всъщност е малко по-мощен от try...finally
, тъй като with
може да проверява и заглушава грешките.
Бъдете внимателни с StopIteration
, но иначе можете да използвате @contextmanager
за създаване на контекстни мениджъри.
person
Veedrac
schedule
29.09.2014
try/finally
ще дадеreferenced before assignment
грешка - person Padraic Cunningham   schedule 29.09.2014__exit__
във вашия код? - person Padraic Cunningham   schedule 29.09.2014__enter__
и__exit__
. - person Dunes   schedule 29.09.2014