I have the following code:
class Test: def __init__(self, name): self.name = name def __enter__(self): print(f'entering {self.name}') def __exit__(self, exctype, excinst, exctb) -> bool: print(f'exiting {self.name}') return True with Test('first') as test: print(f'in {test.name}') test = Test('second') with test: print(f'in {test.name}')
Running it produces the following output:
entering first exiting first entering second in second exiting second
But I expected it to produce:
entering first in first exiting first entering second in second exiting second
Why isn’t the code within my first example called?
Advertisement
Answer
The __enter__
method should return the context object. with ... as ...
uses the return value of __enter__
to determine what object to give you. Since your __enter__
returns nothing, it implicitly returns None
, so test
is None
.
with Test('first') as test: print(f'in {test.name}') test = Test('second') with test: print(f'in {test.name}')
So test
is none. Then test.name
is an error. That error gets raised, so Test('first').__exit__
gets called. __exit__
returns True
, which indicates that the error has been handled (essentially, that your __exit__
is acting like an except
block), so the code continues after the first with
block, since you told Python everything was fine.
Consider
def __enter__(self): print(f'entering {self.name}') return self
You might also consider not returning True
from __exit__
unless you truly intend to unconditionally suppress all errors in the block (and fully understand the consequences of suppressing other programmers’ errors, as well as KeyboardInterrupt
, StopIteration
, and various system signals)