@contextlib.contextmanager
is awesome to easily create contextmanagers without boilerplate. However, creating contextmanager for classes is still painful/boilerplate, expecially when dealing with nested contextmanagers.
It would be great to extend the yield-based syntax of @contextlib.contextmanager
to classes (e.g. through a __contextmanager__
method).
Here is an example how this would look on a (simplified) real use-case I had.
@contextmanager
@dataclass
class File:
path: str
mode: str = 'r'
def __contextmanager__(self):
with tf.io.gfile.GFile(self.path, self.mode) as f:
with h5py.File(f, self.mode) as h5_f:
yield h5_f
... # File has other methods
with File('/path/to/file.txt') as f:
data = f['dataset']
Without __contextmanager__
, implementing __enter__
/__exit__
would be much more verbose/ugly. One would have to save the GFile
, h5py.File
through self._gfile_context
attributes. It’s also not trivial at all to correctly implement __exit__
in case GFile
or h5py.File
suppress the exceptions (GFile.__exit__()
return True
).
The implementation could be a simple extension of contextlib.contextmanager
. Here is a proof of concept:
def contextmanager(cls):
cm: Optional[ContextManager[_T]] = None
def __enter__(self):
nonlocal cm
cm = self.__contextmanager__()
return cm.__enter__()
def __exit__(self, exc_type, exc_value, traceback):
return cm.__exit__(exc_type, exc_value, traceback)
cls.__enter__ = __enter__
cls.__contextmanager__ = contextlib.contextmanager(cls.__contextmanager__)
cls.__exit__ = __exit__
return cls
Note: This implementation also works with inheritance
@contextmanager
class FileWrapper(File):
def __contextmanager__(self):
with super().__contextmanager__() as f:
yield f