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

Reply via email to