How should I correctly nest the with-related behavior of classes (e.g. when deriving or instantiating)?
This works for me but I wonder if there's a dedicated way to do it:
class class_a:
def __init__(self):
print('class_a::__init__')
def __enter__(self):
print('class_a::__enter__')
return self
def __exit__(self, type, exit, tb):
print('class_a::__exit__')
class class_b(class_a):
def __init__(self):
class_a.__init__(self)
print('class_b::__init__')
def __enter__(self):
class_a.__enter__(self)
print('class_b::__enter__')
return self
def __exit__(self, type, exit, tb):
class_a.__exit__(self, type, exit, tb)
print('class_b::__exit__', type, exit, tb)
with class_b():
print('ready')
try:
signal.pause()
except:
pass
One way to do this differently would be to implement class_b like this:
class class_b:
def __init__(self):
self._class_a_inst = class_a()
print('class_b::__init__')
def __enter__(self):
self._class_a_inst.__enter__()
print('class_b::__enter__')
return self
def __exit__(self, type, exit, tb):
self._class_a_inst.__exit__(type, exit, tb)
print('class_b::__exit__', type, exit, tb)
Is there any difference regarding the __enter__() / __exit__() behavior?
Ideally, use contextlib.contextmanager. For the case of deriving:
import contextlib
class context_mixin:
def __enter__(self):
self.__context = self.context()
return self.__context.__enter__()
def __exit__(self, *args):
return self.__context.__exit__(*args)
class class_a(context_mixin):
@contextlib.contextmanager
def context(self):
print('class_a enter')
try:
yield self
finally:
print('class_a exit')
class class_b(class_a):
@contextlib.contextmanager
def context(self):
with super().context():
print('class_b enter')
try:
yield self
finally:
print('class_b exit')
In Python 2, super() needs to be super(class_b, self).
There is a change in behaviour compared with your code: this code exits b before exiting a, meaning that the scopes nest. You've written your code to do them in the other order, although that's easy enough to change. Often it makes no difference, but when it does matter you usually want things to nest. So for an (admittedly-contrived) example, if class_a represents an open file, and class_b represents some file format, then the exit path for class_a will close the file, while the exit path for class_b will write any buffered changes that have yet to be committed. Clearly b should happen first!
For the case of holding another object:
class class_b(context_mixin):
def __init__(self):
self.a = class_a()
@contextlib.contextmanager
def context(self):
with self.a:
print('class_b enter')
try:
yield self
finally:
print('class_b exit')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With