In Python, special attention should be paid to resources such as file reading and writing—they must be properly closed after use. One correct way to close file resources is to use try...finally:
try:
f = open('/path/to/file', 'r')
f.read()
finally:
if f:
f.close()
Writing try...finally is quite cumbersome. Python’s with statement allows us to use resources conveniently without worrying about unclosed resources, so the above code can be simplified to:
with open('/path/to/file', 'r') as f:
f.read()
It is not only the file-like object returned by the open() function that can use the with statement. In fact, any object can be used with the with statement as long as it correctly implements context management.
Context management is implemented through the __enter__ and __exit__ methods. For example, the following class implements these two methods:
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:
with Query('Bob') as q:
q.query()
Implementing __enter__ and __exit__ is still cumbersome, so Python’s standard library contextlib provides a simpler way to write it. The above code can be rewritten as follows:
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, uses the yield statement to pass the variable out via with ... as var, and then the with statement works normally:
with create_query('Bob') as q:
q.query()
In many cases, we want to automatically execute specific code before and after a section of code runs, which can also be achieved with @contextmanager. For example:
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
with tag("h1"):
print("hello")
print("world")
The execution result of the above code is:
<h1>
hello
world
</h1>
The execution order of the code is:
with statement first executes the code before yield, so <h1> is printed;yield call executes all the code inside the with statement, so hello and world are printed;yield is executed, printing </h1>.Therefore, @contextmanager allows us to simplify context management by writing generators.
If an object does not implement context management, we cannot use it with the with statement. In this case, we can use closing() to convert the object into a context object. For example, using urlopen() with the with statement:
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
closing is also a generator decorated with @contextmanager, and this generator is actually very simple to write:
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
Its role is to convert any object into a context object that supports the with statement.
The @contextlib module also provides other decorators to help us write more concise code.