Explaining Python's '__enter__' and '__exit__'

352,944

Solution 1

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.

Solution 2

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 the myfile.txt file 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 management 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.

Solution 3

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. The with 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 be None.

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.

Note that there are further docs here:
https://docs.python.org/3/library/stdtypes.html#context-manager-types

...they have a slightly more detailed explanation of the __enter__ and __exit__ methods. Particularly it is more explicit that __exit__ should return a boolean value (though truthy or falsy will work fine, e.g. an implicit None return will give the default behaviour of propagating the exception).

Solution 4

In addition to the above answers to exemplify invocation order, a simple run example

class MyClass:
    def __init__(self):
        print("__init__")

    def __enter__(self): 
        print("__enter__")

    def __exit__(self, type, value, traceback):
        print("__exit__")
    
    def __del__(self):
        print("__del__")
    
with MyClass(): 
    print("body")

Produces the output:

__init__
__enter__
body
__exit__
__del__

A reminder: when using the syntax with MyClass() as my_handler, variable my_handler gets the value returned by __enter__(), in the above case None! For such use, need to define return value, such as:

def __enter__(self): 
    print('__enter__')
    return self

Solution 5

This is called context manager and I just want to add that similar approaches exist for other programming languages. Comparing them could be helpful in understanding the context manager in python. Basically, a context manager is used when we are dealing with some resources (file, network, database) that need to be initialized and at some point, tear downed (disposed). In Java 7 and above we have automatic resource management that takes the form of:

//Java code
try (Session session = new Session())
{
  // do stuff
}

Note that Session needs to implement AutoClosable or one of its (many) sub-interfaces.

In C#, we have using statements for managing resources that takes the form of:

//C# code
using(Session session = new Session())
{
  ... do stuff.
}

In which Session should implement IDisposable.

In python, the class that we use should implement __enter__ and __exit__. So it takes the form of:

#Python code
with Session() as session:
    #do stuff

And as others pointed out, you can always use try/finally statement in all the languages to implement the same mechanism. This is just syntactic sugar.

Share:
352,944
zjm1126
Author by

zjm1126

Updated on July 18, 2022

Comments

  • zjm1126
    zjm1126 almost 2 years

    I saw this in someone's code. What does it mean?

        def __enter__(self):
            return self
    
        def __exit__(self, type, value, tb):
            self.stream.close()
    

    from __future__ import with_statement#for python2.5 
    
    class a(object):
        def __enter__(self):
            print 'sss'
            return 'sss111'
        def __exit__(self ,type, value, traceback):
            print 'ok'
            return False
    
    with a() as s:
        print s
    
    
    print s
    
  • ViFI
    ViFI almost 8 years
    Probably, __enter__ should return self always as then only other methods of the class can be called on the context.
  • Grief
    Grief almost 8 years
    @ViFI There are 4 examples of def __enter__(self) in PEP 343 and no one does return self: python.org/dev/peps/pep-0343 . Why do you think so?
  • ViFI
    ViFI almost 8 years
    @Grief : For 2 reasons, In my opinion, 1) i won't be able to call other methods on self object as explained here : stackoverflow.com/questions/38281853/… 2) self.XYZ is just part of self object and returning handle only to just that seems inappropriate to me from maintenance point of view. I would rather prefer to return handle to complete object and then provide public APIs to only those components self object,which I want to expose to user like in with open(abc.txt, 'r') as fin: content = fin.read()
  • holdenweb
    holdenweb over 7 years
    File objects return self from __enter__, which is how come you can process the file as f inside with open(...) as f
  • dfrankow
    dfrankow about 7 years
    One subtlety I had to understand: if the object requires parameters to initialize, those should be on init, not self.
  • Sean
    Sean over 6 years
    And even if the sequence of the definitions are switched, the executing order stays the same!
  • meawoppl
    meawoppl about 6 years
    There are a ton of use cases to not return self and in any case that you do need the context object itself, you can always instantiate the context on line before the with inst.
  • Tcll
    Tcll almost 5 years
    might be important to note this buried bit within the PEP reference noting the arg use: python.org/dev/peps/pep-0343/#generator-decorator
  • a135
    a135 about 3 years
    IMO what is returned with __enter__ should be up to the developer(s) of the class as long as the documented usage seems logical to the user(s) of the class: 1. Open a resource that is not meant to stay open 2. Use the resource, call certain methods on the resource, etc 3. The resource is closed
  • Gigi
    Gigi over 2 years
    link is broken for effbot