Python

with Statement

The Python with statement is a very advanced feature that helps to implement the context management protocol. When the programmer starts coding, they are basically using the try/except/finally to maintain the resources. But there is another way to do this automatically, called the ‘with’ statement.

So, in this article, we will discuss how we can use the ‘with‘ statement.

We can understand this with a very simple example.

Whenever we code something to read or write a file, the first thing which we have to do is to open the file, and then we perform the read or write operations on that and, at last, we close the file so that all the resources will not be busy. So it means that we have to release the resource after we complete our work.

This we can also understand from the context manager. A context manager is an object which takes care of the resources to save, restore, lock or unlock resources, opening and closing files, etc. The context manager is active when we open a file for reading or writing. If we do not close the file after reading or writing, then the resource is still allocated to that particular file, and due to this, the resource will be busy if a particular process wants to use that resource.

That’s why we call the close () method after reading or writing the file.

f = open("demo.txt")

data = f.read()

f.close()

So, in the above code, it is straightforward, and there is no exception handler we used. So, if any error occurs, the program will stop unconditionally. And the second case is that sometimes we also forget to add the close file as we did.

So to overcome some of the problems, we can use the following method to write the above code.

try:
    f = open('demo.txt', 'r')
    print(f.read())

except Exception as e:
    print("Error occurred ", e)

finally:
    f.close()

In the above code, you can see we used the try, except and finally block. So, in this way, we can control the exception handling too. And we close the file at last in the finally block. We also used the except block, which will handle the exceptions. So if we use the above scenario, our code will not stop unconditionally. And our file surely will close even if we get some error during reading the file.

But we can also refine the above code through another method which we called the ‘with’ statement. The ‘with’ statement will automatically handle the file’s closing, and we don’t have to care about closing the file after reading or writing.

The context manager creates an enter () and exit () method at run time and calls it when they have to destroy the file. While doing simple code or with a try, except block, we call the exit () method through the close () method. But the ‘with’ statement automatically takes care of the exit () method. So this is the beauty of the ‘with’ statement.

We can re-write the above code with the ‘with’ statement as below:

with open("demo.txt") as f:

    data = f.read()

The above code is very simple, and we don’t have to think about it every time we close the file, which is done by the ‘with’ statement automatically.

This looks like magic, but actually, this is not magic. The ‘with’ statement initiates two objects which we called __enter__ () and __exit__ (). The statement that follows the ‘with’ statements is called __enter__ () and returns an object assigned to as variable, and after all the block process is done, it calls the __exit__ ().

Example_1: ex1.py

#ex1.py

class Demo:
    def __enter__(self):
        print(" calling to __enter__ method")
        return "True"

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(" calling to __exit__ method")


def calling_demo():
    return Demo()


with calling_demo() as f:
    print("demo:", f)

Output:

➜  ~ cd Desktop
➜  Desktop python3 ex1.py
 calling to __enter__ method
demo: True
 calling to __exit__ method
➜  Desktop

Explanation:

  1. When we run the above code ex1.py, then it’s first called the method __enter__.
  2. Then it returns something from the code (True) assigned to the variable f.
  3. Then, the block of the code was executed. In that block, we are printing the value of the f, which is True.
  4. At last, when the process of the block is over, then it is called the method __exit__.

The best thing about the ‘with’ statement is that it automatically handles the exception too. As you can see in the above code example ex1.py, the __exit__ method takes three parameters: exc_type, exc_val, exc_tb. These parameters help to handle the exceptions.

Syntax: __exit__(self, exc_type, exc_value, exc_tb)

exc_type: It tells the name of the class where an exception occurs.
exc_value: It tells the type of the exception like divide by zero error, etc.
exc_traceback: The traceback is full detail about the exception, just like a report to solve the error that occurred in the program.

Now, we will change the above code to see how it will automatically handle the exceptions.

Example_2: ZeroDivisionError.py

#ZeroDivisionError.py

class Demo:
    def __init__(self, x, y):
        print("Enter __init__")
        self.x = x
        self.y = y
 
    def __enter__(self):
        print("Find the __enter__")
        return self
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("\Find the __exit__")
        print("\ntype: ", exc_type)
        print("\nvalue: ", exc_val)
        print("\nTraceback: ", exc_tb)
 
    def exceptionDemo(self):
        # ZeroDivisionError exception
        print(self.x / self.y)
 
 
# with statement not raise exception
with Demo(4, 2) as f:
    f.exceptionDemo()
 
print("\n\n=======================================\n\n")
 
# with statement will raise a ZeroDivisionError
with Demo(1, 0) as f:
    f.exceptionDemo()

Output:

➜  Desktop python3 zeroDivisonError.py
Enter __init__
Find the __enter__
2.0
\Find the __exit__

type:  None

value:  None

Traceback:  None


=======================================


Enter __init__
Find the __enter__
\Find the __exit__

type:  

value:  division by zero

Traceback:  
Traceback (most recent call last):
  File "zeroDivisonError.py", line 32, in
    f.exceptionDemo()
  File "zeroDivisonError.py", line 21, in exceptionDemo
    print(self.x / self.y)
ZeroDivisionError: division by zero
➜  Desktop

Explanation:
In the above code, line number 25, we run the code with the ‘with’ statement. In that, we pass the value of x as 4 and y as 2. In the output section, we can see that it first calls the __init__method and initializes x and y. Then it calls the __enter__ method and assigned that object to the variable f.Then it calls the exceptionDemo method using the f variable and then prints the division value, which is 2. After that, it calls the __exit__ method and then prints all the three important parameters values None because we don’t have any error till now.

At line number 31, we call the same method with the value of x as 1 and y as 0 because we want to raise the exception and see how the ‘with’ statement handles it without the try and except block. We can see that in the output section, the three-parameter values are different.

The first parameter (exc_type) types printing the class name, which caused an error.

The second parameter (exc_val) prints the type of error.

The third parameter (exc_tb) printing the Traceback details.

Conclusion:
So, we have seen how the ‘with’ statement actually performs smartly to handle the exception handling automatically. The ‘with’ statement also helps to close the context manager properly, which may be leaving open during the programming.

This article code is available on the github link:
https://github.com/shekharpandey89/with-statement

About the author

Shekhar Pandey