Appearance
contextlib
In Python, it's crucial to properly close resources like files after use. One traditional way to ensure this is by using try...finally
:
python
try:
f = open('/path/to/file', 'r')
f.read()
finally:
if f:
f.close()
However, writing try...finally
can be cumbersome. Python's with
statement allows us to easily manage resources without worrying about forgetting to close them, simplifying the code to:
python
with open('/path/to/file', 'r') as f:
f.read()
The with
statement can be used with any object that correctly implements context management. Context management is achieved through the __enter__
and __exit__
methods. For example, the following class implements these methods:
python
class Query(object):
def __init__(self, name):
self.name = name
def __enter__(self):
print('Begin')
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
print('Error')
else:
print('End')
def query(self):
print('Query info about %s...' % self.name)
Now, we can use our custom resource object with the with
statement:
python
with Query('Bob') as q:
q.query()
@contextmanager
Implementing __enter__
and __exit__
can still be quite verbose, so Python's standard library contextlib
provides a simpler way to achieve this with the @contextmanager
decorator. The previous code can be rewritten as follows:
python
from contextlib import contextmanager
class Query(object):
def __init__(self, name):
self.name = name
def query(self):
print('Query info about %s...' % self.name)
@contextmanager
def create_query(name):
print('Begin')
q = Query(name)
yield q
print('End')
The @contextmanager
decorator accepts a generator function, yielding the variable to be used in the with ... as var
statement. The with
statement will then function correctly:
python
with create_query('Bob') as q:
q.query()
Often, we want to automatically execute specific code before and after a block of code, which can also be implemented with @contextmanager
. For example:
python
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
with tag("h1"):
print("hello")
print("world")
The output of the above code will be:
<h1>
hello
world
</h1>
The execution order is as follows:
- The
with
statement first executes the code beforeyield
, printing<h1>
. - The
yield
call executes all statements inside thewith
block, printinghello
andworld
. - Finally, the code after
yield
executes, printing</h1>
.
Thus, @contextmanager
simplifies context management by allowing us to write generators.
@closing
If an object does not implement context management, it cannot be used with the with
statement. In such cases, we can use closing()
to convert that object into a context object. For instance, using urlopen()
with a with
statement:
python
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
The closing
function is also a generator decorated with @contextmanager
, which is quite simple to write:
python
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
Its purpose is to turn any object into a context object that supports the with
statement.
The @contextlib
module includes other decorators that help us write more concise code as well.