I found the following example of a Context Manager for a File
object:
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
self.file_obj.close()
Here, the work done by the manager, that is actually opening the file, happens in the __init__
method. However, in the accompanying text, they suggest that the file opening should happen in the __enter__
call:
Let’s talk about what happens under-the-hood.
- The with statement stores the exit method of the File class.
- It calls the enter method of the File class.
- The enter method opens the file and returns it.
- The opened file handle is passed to opened_file.
- We write to the file using .write().
- The with statement calls the stored exit method.
- The exit method closes the file.
Which is the correct approach in general? It seems to be that the work undone by __exit__
should happen in __enter__
, not __init__
since those are paired 1:1 by the context manager mechanism, but this example leaves me doubtful.
CodePudding user response:
There is no general answer. It depends on what the work is. For example, for a file, opening happens in __init__
, but for a lock, locking happens in __enter__
.
One important thing to think about is, what should happen if the object is not used as a context manager, or not immediately used as a context manager? What should the object's state be after construction? Should relevant resources already be acquired at that point?
For a file, the answer is yes, the file should already be open, so opening happens in __init__
. For a lock, the answer is no, the lock should not be locked, so locking goes in __enter__
.
Another thing to consider is, should this object be usable as a context manager more than once? If entering a context manager twice should do a thing twice, that thing needs to happen in __enter__
.
CodePudding user response:
Hereis a better example
class TraceBlock:
def message(self, arg):
print('running ' arg)
def __enter__(self):
print('starting with block')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if exc_type is None:
print('exited normally\n')
else:
print('raise an exception! ' str(exc_type))
return False # Propagate
#--------------------------
if __name__ == '__main__':
with TraceBlock() as action:
action.message('test 1')
print('reached')
with TraceBlock() as action:
action.message('test 2')
raise TypeError
print('not reached')
If The Exit returns False the exception will pass on to other handler, if it return True the exception would not go to others.