__enter__(self) is called on the object that expr evaluates to. The object that __enter__() returns is assigned to the variableobj.
When the with block is exited, the Python interpreter calls __exit__(…) on the object that expr evaluated to.
Typically, __enter__ acquires a resource and __exit__ releases it. Thus, when a resource is used in a with statement, we're sure that the resource will be released when the with block is left. A well known example are files:
with open(…) as f:
… do something with f
# file will be closed here.
No new scope is established
The with … as var statement does not establish a new scope outside of which var is not visible:
def write_a_line(txt):
file_h.write(f'{txt}\n')
with open('test.txt', 'w') as file_h:
#
# file_h is visible in function above,
# outside of this «with block».
#
write_a_line('first line')
write_a_line('second line')
Simple demonstration
The following simple script tries to demonstrate how with works behind the scenes:
class CLS:
def __init__(self, ident):
self.ident = ident
print(f'__init__, ident = {self.ident}')
def identify(self):
print(f'I am {self.ident}')
def __enter__(self):
print(f'{self.ident}: __enter__')
return CLS('created in __enter__')
def __exit__(self, type, value, traceback):
print(f'{self.ident}: __exit__')
print('Going to use with statement:')
with CLS('xyz') as obj:
obj.identify()
print('Finished with with statement')
Going to use with statement:
__init__, ident = xyz
xyz: __enter__
__init__, ident = created in __enter__
I am created in __enter__
xyz: __exit__
Finished with with statement