Python Basics
Context Management
Context managers allow to manage external ressources. It usually works with
ressources that need to be opened, managed and then closed. The with
statement allow to work
specific objects for which a context manager exists.
Tristan
contact-datartichaut@pm.me
Python projects list
Libraries.
import os
Beginner
What are context managers ?
Context managers are a clean way to interact with external ressources in python.
Let's look at an example.
ToC
# Accessing the file with a context manager ...
with open('file.txt', 'r') as file:
# ... The file is used inside the context manager.s
print('File content : ')
print(file.read())
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
In this simple example, we accessed a file, read its content and checked that the file was properly closed in the end.
This can be done without a context manager, like so :
# Setting up the ressource.
file = open('file.txt', 'r')
# Using the ressource.
print('File content : ')
print(file.read())
# Tearing down the ressource.
file.close()
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
The result appear to be the same so this raise the question :
Why use a context manager ?
When to use a context manager ?
Intermediate
Context manager alternative.
There is a possible alternative to using a context manager. A try / finally
block can
do the trick. Our example then becomes :
ToC
# The ressource is set up and used inside the try clause to catch any error at this point.
try:
# Setting up the ressource.
file = open('file.txt', 'r')
# Using the ressource.
print('File content : ')
print(file.read())
finally:
# Tearing down the ressource.
file.close()
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
The end result is the same, but the context manager version is considered more pythonic, and is a but cleaner.
Intermediate
Creating a context manager.
Creating a context manager can be useful and is not overly complicated.
The following example is a simple file handler. It does not have any practical use because, it basically
does what the open
function does, but it is a good example.
ToC
class Open_file:
"""A file opener class."""
def __init__(self, file_name, mode):
self.file_name = file_name
self.mode = mode
First let's see what happen if we try to use a simple class in a context manager.
try:
with Open_file(file_name='file.txt', mode='r'):
pass
except AttributeError:
print("Open_file does not have a '__enter__' and '__exit__' method.")
Python rely on the '__enter__' method to set up the ressource and on the '__exit__' method to tear it down.
Let's add those to the Open_file
class.
class Open_file:
"""A file opener class."""
def __init__(self, file_name, mode):
self.file_name = file_name
self.mode = mode
def __enter__(self):
"""Set up the file."""
self.file = open(self.file_name, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, traceback):
"""Tear down the file."""
self.file.close()
Now let's test try this out.
# Accessing the file with our context manager ...
with Open_file('file.txt', 'r') as file:
# ... The file is used inside the context manager.s
print('File content : ')
print(file.read())
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
The new context manager works as intendend. Let's provoke an error to see if the file is still closed properly.
# Accessing the file with our context manager ...
try:
with Open_file('file.txt', 'r') as file:
# ... The file is used inside the context manager.s
print('File content : ')
print(file.read())
10 / 0
except ZeroDivisionError:
print('Forced error.')
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
The error occurs inside our context manager, so before reaching the end of the context manager block that is supposed to handle the tearing down of the file, but we can see that the tearing down step still occurs.
Advanced
Creating a context manager (advanced).
There is a slightly more complex but more elegant way of creating a context manager. It is not a
lot more complicated than with a class but it requires a few more intermediate notions : the yield
statement and decorators.
Let's rewrite our last context manager.
ToC
from contextlib import contextmanager
@contextmanager
def open_file(file_name, mode):
"""Open and manage a file."""
try:
file = open(file_name, mode)
yield file
finally:
file.close()
The contextmanager
decorator from the contextlib
library allow to simply create
a context manager using a yield
statement.
The yield statement signals that at this point, the function is no longer executed, the file is returned and the rest of the function is only exectued when we reach the end of the context manager's block. Let's see it in practice :
# Accessing the file with our context manager ...
with open_file('file.txt', 'r') as file:
# ... The file is used inside the context manager.s
print('File content : ')
print(file.read())
# Cheking if the file is properly closed.
print(f'Is the file properly closed : {file.closed}')
Our context manager work properly.
Advanced
A practical example.
The last example is not very useful because the open
built-in function already does
all that.
A more practical example would be a context manager that allow to work in a specific directory on the machine. This would allow to go to a location, handle so files, and wathever happens to return to the original location.
ToC
@contextmanager
def change_dir(str_path):
"""Change the working directory."""
try:
# Storing the current working directory in memory to be able to get back to it later.
str_cwd = os.getcwd()
# Changing the working directory than yielding. The yield does not need to return an object in this case.
os.chdir(str_path)
yield
finally:
# Whatever happens, the current working directory is reverted back to the first one.
os.chdir(str_cwd)
This context manager allow to execute a specific piece of code inside a directory and go back to the original working directory when we are done.
with change_dir('/Users/tristan/Documents/'):
with open_file('test.txt', 'w') as file:
file.write('A sample text.')
Let's see if this worked.
with change_dir('/Users/tristan/Documents/'):
with open_file('test.txt', 'r') as file:
str_text = file.read()
print(str_text)
os.remove('test.txt')
We were able to access and read the file created before.