Explaining Python's '__enter__' and '__exit__'
Using these magic methods (__enter__
, __exit__
) allows you to implement objects which can be used easily with the with
statement.
The idea is that it makes it easy to build code which needs some 'cleandown' code executed (think of it as a try-finally
block). Some more explanation here.
A useful example could be a database connection object (which then automagically closes the connection once the corresponding 'with'-statement goes out of scope):
class DatabaseConnection(object):
def __enter__(self):
# make a database connection and return it
...
return self.dbconn
def __exit__(self, exc_type, exc_val, exc_tb):
# make sure the dbconnection gets closed
self.dbconn.close()
...
As explained above, use this object with the with
statement (you may need to do from __future__ import with_statement
at the top of the file if you're on Python 2.5).
with DatabaseConnection() as mydbconn:
# do stuff
PEP343 -- The 'with' statement' has a nice writeup as well.
If you know what context managers are then you need nothing more to understand __enter__
and __exit__
magic methods. Lets see a very simple example.
In this example I am opening myfile.txt with help of open function. The try/finally block ensures that even if an unexpected exception occurs myfile.txt will be closed.
fp=open(r"C:\Users\SharpEl\Desktop\myfile.txt")
try:
for line in fp:
print(line)
finally:
fp.close()
Now I am opening same file with with statement:
with open(r"C:\Users\SharpEl\Desktop\myfile.txt") as fp:
for line in fp:
print(line)
If you look at the code, I didn't close the file & there is no try/finally block. Because with statement automatically closes myfile.txt . You can even check it by calling print(fp.closed)
attribute -- which returns True
.
This is because the file objects (fp in my example) returned by open function has two built-in methods __enter__
and __exit__
. It is also known as context manager. __enter__
method is called at the start of with block and __exit__
method is called at the end. Note: with statement only works with objects that support the context mamangement protocol i.e. they have __enter__
and __exit__
methods. A class which implement both methods is known as context manager class.
Now lets define our own context manager class.
class Log:
def __init__(self,filename):
self.filename=filename
self.fp=None
def logging(self,text):
self.fp.write(text+'\n')
def __enter__(self):
print("__enter__")
self.fp=open(self.filename,"a+")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("__exit__")
self.fp.close()
with Log(r"C:\Users\SharpEl\Desktop\myfile.txt") as logfile:
print("Main")
logfile.logging("Test1")
logfile.logging("Test2")
I hope now you have basic understanding of both __enter__
and __exit__
magic methods.
I found it strangely difficult to locate the python docs for __enter__
and __exit__
methods by Googling, so to help others here is the link:
https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers
(detail is the same for both versions)
object.__enter__(self)
Enter the runtime context related to this object. Thewith
statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.
object.__exit__(self, exc_type, exc_value, traceback)
Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will beNone
.If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
Note that
__exit__()
methods should not reraise the passed-in exception; this is the caller’s responsibility.
I was hoping for a clear description of the __exit__
method arguments. This is lacking but we can deduce them...
Presumably exc_type
is the class of the exception.
It says you should not re-raise the passed-in exception. This suggests to us that one of the arguments might be an actual Exception instance ...or maybe you're supposed to instantiate it yourself from the type and value?
We can answer by looking at this article:
http://effbot.org/zone/python-with-statement.htm
For example, the following
__exit__
method swallows any TypeError, but lets all other exceptions through:
def __exit__(self, type, value, traceback):
return isinstance(value, TypeError)
...so clearly value
is an Exception instance.
And presumably traceback
is a Python traceback object.