As part of my series on Python&aposs syntactic sugar, I want to tackle unravelling the with
statement.
Looking at the bytecode for a simple with
statement, you will notice there are a lot of opcodes being used. Most of it is managing the execution stack for the interpreter and dealing with potential exceptions. As such, I&aposm going to skip covering the bytecode and instead go with the unravelled syntax as provided by the language reference (touched up for easier reading):
# with a as b:
# c
_enter = type(a).__enter__
_exit = type(a).__exit__
b = _enter(a)
try:
c
except:
if not _exit(a, *sys.exc_info()):
raise
else:
_exit(a, None, None, None)
Unravelling a with
statement
To help illustrate how this all works, we are going to use the classic RAII lock example throughout this blog post. If you&aposre unfamiliar with RAII, it stands for "resource acquisition is initialization". It basically means that by creating an object something happens, and by freeing/deleting the object that something is undone. For locks it means allocating the lock upon creation and then releasing the lock upon deletion. Because Python as a language has no guaranteed cleanup semantics in terms of time and order, context managers were introduced to explicitly bring a language construct to Python that lets us get the benefits of the RAII concept.
import threading
lock = threading.Lock()
with lock: # The lock is held.
pass # Something needing the lock.
# The lock has been released.
Example use of a lock as a context manager
There are two parts to a context manager. One is calling its __enter__
special method when entering the with
block; for our locking example, this is calling threading.Lock.acquire()
. If you use the as
clause of a context manager, the object the __enter__
method returns is what gets bound to the variable you specified.
The second part of a context manager is the __exit__
special method when the with
block is exited; this is calling threading.Lock.release()
in our locking example. The key detail about __exit__
involves whether an exception was raised in the body of the with
block. If an exception was raised, it is passed in by its constituent parts as returned by *sys.exc_info()
to __exit__
; if no exception is raised then None
is given for those three parts instead. If an exception happens to be raised in the body of the context manager, the __exit__
method can choose to either suppress or allow the exception to continue to propagate. Which action is taken is controlled by whether a true or false value is returned by the method, respectively. The meaning of the return value is this way so that exceptions will be re-raised thanks to the fact that methods by default return None
.
If you would like more historical details, PEP 343 is what brought context managers to Python.
from Planet Python
via read more
No comments:
Post a Comment