context manager exit called before code in body
-
30-06-2021 - |
Pergunta
I've been playing around with making my own context managers in Python. I'm seeing some strange behavior most likely due to my implementation.
I see the __exit__
code called before a statement in the 'with' context. For example, here is the code snippet:
with ProgressBar(10) as p:
p.update(1)
and it's exception:
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
AttributeError: 'NoneType' object has no attribute 'update'
I put debug in all the __enter__
, __exit__
, and update methods of my context manager. It looks like __exit__
is called before update(). This makes no sense so I must be missing something simple.
Here is my simple context manager class:
class ProgressBar(object):
"""Progress bar that normalizes progress to [0 - 100] scale"""
def __init__(self, max_value):
"""Create progress bar with max_value"""
self._current_value = 0.0
self._completed_value = 100.0
self._max_value = float(max_value)
print 'init', max_value
def __enter__(self):
"""Start of context manager, 'with' statement"""
print 'enter'
self._current_value = 0.0
def __exit__(self, exc_type, exc_value, traceback):
"""Start of context manager, 'with' statement"""
print 'exit'
self._current_value = self._completed_value
# Not handling any exceptions, so they'll be raised automatically
# To ignore exceptions return True or inspect arguments to handle
return False
def update(self, value):
"""Update progress value"""
print 'update'
if value >= self._max_value:
self._current_value = 100
else:
self._current_value = (value / self._max_value) * self._completed_value
print '\r%s' % (self._current_value),
Solução
From the documentation:
object.__enter__(self)
Enter the runtime context related to this object. The
with
statement will bind this method’s return value to the target(s) specified in theas
clause of the statement, if any.
You're not returning anything from __enter__
(and thus you're returning None, as always). If you return self
, you'll get
init 10
enter
update
10.0 exit
Outras dicas
The with
statement binds the return value of a context manager __enter__
method to the variable. In your case that means p
is bound to None
(your __enter__
doesn't have a return statement at all, so None
is the default), an AttributeError
is raised and the __exit__
method is called.
The solution is to return self
from the __enter__
method:
def __enter__(self):
"""Start of context manager, 'with' statement"""
print 'enter'
self._current_value = 0.0
return self