Invoking a constructor in a 'with' statement
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?
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)
The problem is that your __enter__
method returns None
. Hence, test
is assigned None
.
Then you try to access (None).name
, which raises an error. Since your __exit__
method returns True
always, it will suppress any errors. According to the docs:
Returning a true value from this method will cause the with statement to suppress the exception and continue execution with the statement immediately following the with statement.