New submission from Luca Mattiello <lucae.mattie...@gmail.com>: Reading the contextlib documentation, one might assume the following to be functionally equivalent, when used in a with statement:
@contextlib.contextmanager def managed_resource(): resource = acquire() try: yield resource finally: resource.release() class ManagedResource: def __init__(self): self.resource = acquire() def __enter__(self): return self.resource def __exit__(self, *args): self.resource.release() However, the first version has a seemingly unexpected behavior when used in conjunction with an ExitStack, and pop_all(). with contextlib.ExitStack() as es: r = es.enter_context(managed_resource()) es.pop_all() # Uh-oh, r gets released anyway with contextlib.ExitStack() as es: r = es.enter_context(ManagedResource()) es.pop_all() # Works as expected I think the reason is https://docs.python.org/3/reference/expressions.html#yield-expressions, in particular > Yield expressions are allowed anywhere in a try construct. > If the generator is not resumed before it is finalized (by > reaching a zero reference count or by being garbage collected), > the generator-iterator’s close() method will be called, > allowing any pending finally clauses to execute. I guess this is working according to the specs, but I found it very counter-intuitive. Could we improve the documentation to point out this subtle difference? Full repro: import contextlib @contextlib.contextmanager def cm(): print("acquire cm") try: yield 1 finally: print("release cm") class CM: def __init__(self): print("acquire CM") def __enter__(self): return 1 def __exit__(self, *args): print("release CM") def f1(): with contextlib.ExitStack() as es: es.enter_context(cm()) es.pop_all() def f2(): with contextlib.ExitStack() as es: es.enter_context(CM()) es.pop_all() f1() f2() Output: acquire cm release cm acquire CM ---------- assignee: docs@python components: Documentation, Library (Lib) messages: 394948 nosy: docs@python, lucae.mattiello priority: normal severity: normal status: open title: contextmanager + ExitStack.pop_all() versions: Python 3.6, Python 3.7, Python 3.8, Python 3.9 _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue44292> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com