Exploring Ptyhon’s with statement

The with statement

mag
Python’s with statement has bewildered me for a while. I never really felt the need to use it. So in this post I’m going to try to explore a bit more about it. A common example of with is to use it for files


with open("file.txt","w") as f:
    f.write("hello\n")
    f.write("bye")
    

with, for your own class

Let’s create our own class and see if we can use it with the with statement.


class MyClass:
    def __init__(self):
        print "Initializing an Awesome Class"
        
    def doSomething(self):
        print "Doing Something"
        
    
with MyClass() as myObject:
    myObject.doSomething()
 

And…..we have an error


Initializing an Awesome Class
Traceback (most recent call last):
  File "test.py", line 10, in <module>
    with MyClass() as myObject:
AttributeError: MyClass instance has no attribute '__exit__'

Upon closer inspection you’ll notice that Python complains about our Class not having an __exit__ method, so let’s give it one.



class MyClass:
    def __init__(self):
        print "Initializing an Awesome Class"
        
    def doSomething(self):
        print "Doing Something"
        
    def __exit__(self):
        print "Just Exiting as one should"
        

with MyClass() as myObject:
    myObject.doSomething()

Arrgghhh….another error

This time Python complains about an __enter__ method. All right Mr. Python Interpreter, as you say.



class MyClass:
    def __init__(self):
        print "Initializing an Awesome Class"
        
    def doSomething(self):
        print "Doing Something"
        
    def __exit__(self):
        print "Just Exiting as one should"
        
    def __enter__(self):
        print "Knock Knock"

with MyClass() as myObject:
    myObject.doSomething()

What now ?


Initializing an Awesome Class
Knock Knock
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    myObject.doSomething()
TypeError: __exit__() takes exactly 1 argument (4 given)

So __enter__ is working fine,but Python is supplying 4 arguments to __exit__
Let’s change the prototype and see what those 4 arguments are.

class MyClass:
    def __init__(self):
        print "Initializing an Awesome Class"
         
    def doSomething(self):
        print "Doing Something"
         
    def __exit__(self,arg1,arg2,arg3):
        print "Just Exiting as one should"
        print 'arg1 = ',arg1,'of type ',type(arg1)
        print 'arg2 = ',arg2,'of type ',type(arg2)
        print 'arg3 = ',arg3,'of type ',type(arg3)
         
    def __enter__(self):
        print "Knock Knock"


 
with MyClass() as myObject:
    myObject.doSomething()

And………another error

We can infer from this traceback that myObject is None. The __exit__ method tells us more about the error that occurred . This highlights the philosophy behind the __exit__ method. It is supposed to handle any errors that might occur in the with clause. The arguments to it are the type of error, the error object and the traceback object respectively. It might also be used to perform clean up if no errors occur. For eg: Closing a file.

The with clause relies on __enter__ and __exit__ . The object returned by one of these must go into myObject. Since __exit__ would be performed at the end, this leaves __enter__ to return the required object.

I am just returning the object itself, but it can return anything that can be intended to be used in the with clause.

class MyClass:
    def __init__(self):
        print "Initializing an Awesome Class"
         
    def doSomething(self):
        print "Doing Something"
         
    def __exit__(self,arg1,arg2,arg3):
        print "Just Exiting as one should"
        print 'arg1 = ',arg1,'of type ',type(arg1)
        print 'arg2 = ',arg2,'of type ',type(arg2)
        print 'arg3 = ',arg3,'of type ',type(arg3)
         
    def __enter__(self):
        print "Knock Knock"
        return self


 
with MyClass() as myObject:
    myObject.doSomething()

Finalllyyy

Initializing an Awesome Class
Knock Knock
Doing Something
Just Exiting as one should
arg1 =  None of type  <type 'NoneType'>
arg2 =  None of type  <type 'NoneType'>
arg3 =  None of type  <type 'NoneType'>

As you can see, since no errors occur, the __exit__ method is supplied all None arguments.

The point behind explaining it this way is to highlight Python’s amazing debugging ability. We have seen the usage and predicted the philosophy behind the with clause without referring the docs. But I’ll highly recommend reading them to get the complete picture.

Read the docs here

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s