Reputation: 1506
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 file
s 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__
?
Upvotes: 8
Views: 2537
Reputation: 45291
To provide context manager functionality for instance members, one might do something like this:
class MemberManager:
managed_member_names = ('a', 'b', 'c')
def __init__(self, a, b, c):
self.a, self.b, self.c = a, b, c
def __enter__(self):
# yield statement means this enter method returns a generator
for i in (getattr(self,n) for n self.managed_member_names):
with open(i, mode="w") as x:
# yield prevents the context manager from exiting
yield x
def __exit__(self, exc_type, exc_value, traceback):
# all items will be closed by __enter__ context manager; nothing needed here
pass
mm = MemberManager(fname1, fname2, fname3)
with mm as open_members:
# open_members is a generator/iterator
for member in open_members:
member.write("foo")
However, note that you cannot do this:
with mm as open_members:
open_member_list = list(open_members)
open_member_list[0].write("foo") # ValueError: I/O operation on closed file.
The open_members
iterator must remain unexhausted for the current file to remain open.
Upvotes: 0
Reputation: 40833
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.
Upvotes: 2
Reputation: 2144
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
.
Upvotes: 4
Reputation: 2427
__enter__
and __exit__
special functions are almost never called manually. There are respectively called when entering and leaving a with
block statement. So if you use something like:
with C() as c:
# stuff
# other stuff
you call these magic functions. In you case, I would call the file open
functions in __enter__
and the corresponding close
function in __exit__
For instance:
class C:
def __enter__(self):
self.a = open('foo')
self.b = open('bar')
return self
def __exit__(self, exc_type, exc_value, traceback):
self.a.close()
self.b.close()
def readline_a(self):
return self.a.readline()
def readline_b(self):
return self.b.readline()
with C() as c:
print(c.readline_a())
print(c.readline_b())
prints the first line of each file foo
and bar
Upvotes: 0