Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call __exit__ on all members of a class

Is there a Pythonic way to automatically __exit__ all members of a class?

class C:
    def __init__(self):
        self.a = open('foo')
        self.b = open('bar')

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        # Is it correct to just forward the parameters here?
        self.a.__exit__(self, exc_type, exc_value, traceback)
        self.b.__exit__(self, exc_type, exc_value, traceback)

Can I do this without manually calling __exit__ on a and b? Am I even calling __exit__ correctly?

Suppose that the resources I have aren't files like in the example and there isn't a method like close or destroy. Is it perhaps good practice to implement a method like this on top of the __enter__ and __exit__?

like image 848
Enn Michael Avatar asked Apr 17 '18 10:04

Enn Michael


2 Answers

So as I mentioned in comment :

I think most helpfull here will be : contextlib.ExitStack

You can create this object as a member of Your own class in the __init__. And then add to it all your depending contextmanagers with the enter_context(cm) where cm is context_manager

def __init__(self):
    self.exit_stack = contextlib.ExitStack()
    self.exit_stack.__enter__()
    self.exit_stack.enter_context(open('foo')) 
    ....

to clear all the depending contexts just call in the __exit__ the exit of this stack.

Or better just subclass ExitStack and in init call the enter_context.

like image 171
Take_Care_ Avatar answered Sep 23 '22 13:09

Take_Care_


Writing your own __enter__ and __exit__ functions tends to not be a great idea. You need to understand what should happen if an exception occurs during a child __exit__ or what it means if __exit__ returns a truthful value.

It's generally better just to write a generator-based context manager instead. eg.

from contextlib import contextmanager

class A:
    def __init__(self, filename0, filename1, file0, file1):
        self.filename0 = filename0
        self.filename1 = filename1
        self.file0 = file0
        self.file1 = file1

    @classmethod
    @contextmanager
    def create(cls, filename0, filename1):
        with open(filename0) as file0, \
                open(filename1) as file1:
            yield cls(filename0, filename1, file0, file1)

with A.create('file0.txt', 'file1.txt') as a:
    a.do_something()

This will open the child context managers in defined order, and automatically close them in a defined order, propagating exceptions and return values correctly.

like image 45
Dunes Avatar answered Sep 26 '22 13:09

Dunes