Objects designed to be used in a with
statement:
with context_manager:
# do stuff
pass
with
is evaluated to a value, which must be a context manager.with
statement use that value in specific ways to implement semantics of with
statemententer()
with
statement's code block beginsexit()
with
statement's code block ends (even if it exits with an exception)__enter__(self)
__exit__(self, exc_type, exc_val, exc_tb)
with
Statementwith expression as x:
body
with
statement executes its expression
, which must evaluate to a context-managerwith
statement then calls __enter__()
on the context-manager from expression
__enter__()
throws an exception, execution never enter with
block and statement is finishedwith
statement includes an as
clause, then return value of __enter__()
is bound to name in the as
clause
expression.__enter__()
and not the return value of expression
that is bound in as
clausebody
is executed and can terminate in one of two fundamental ways:
__exit__()
is called after block is terminated
__exit__()
is called with no extra information__exit__()
LoggingContextManager
class LoggingContextManager:
def __enter__(self):
print('LoggingContextManager.__enter__()')
return 'You are in a with-block!'
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is None:
print('LoggingContextManager.__exit__: normal exit detected')
else:
print('LoggingContextManager.__exit__:'
'Exception detected!'
'type={}, value={}, traceback={})'.format(
exc_type, exc_val, exc_tb
))
return
__enter__()
with
-statement bodyas variable
__exit__()
with
-statement body exits
with
block exits with exception
__exit__()
should accept 3 arguments:
exc_type
- exception typeexc_val
- exception objectexc_tb
- exception traceback__exit__()
propagates exceptions thrown from with
-statement's code block__exit__()
returns value that evaluates to False
, exception is propagated
__exit__()
should answer the question "should the with
-statement swallow exceptions?"__exit__()
should just return False
and let with
-statement re-raise itcontextlib
with
statement"contextlib.contextmanager
import contextlib
@contextlib.contextmanager
def my_context_manager():
# <ENTER>
try:
yield [value] # <= like __enter__()'s return statement
# <NORMAL EXIT>
except:
# <EXCEPTIONAL EXIT>
raise
with my_context_manager() as x:
# ...
LoggingContextManager
refactored using contextmanager
decoratorimport contextlib
import sys
@contextlib.contextmanager
def logging_context_manager():
print('logging_context_manager: enter')
try:
yield 'You are in a with-block!'
print('logging_context_manager: normal exit')
except Exception:
print('logging_context_manager: exceptional exit',
sys.exc_info())
raise
with
-statements can use as many context-managers as necessarywith cm1() as a, cm2() as b, ...:
BODY
# NOTE: multiple syntax is the same as having them nested
with cm1() as a:
with cm2() as b:
BODY
import contextlib
@contextlib.contextmanager
def nest_test(name):
print('Entering', name)
yield
print('Exiting', name)
with nest_test('outer') as a, nest_test('inner') as b:
print('BODY')
with nest_test('outer') as a:
with nest_test('inner') as b:
print('BODY')
# NOTE: both of the above `with`-statements output the same thing
#=> Entering outer
#=> Entering inner
#=> BODY
#=> Exiting inner
#=> Exiting outer
Connection
class which represents some sort of database connectionTransaction
class which manages transactions in database# db/connection.py
class Connection:
def __init__(self):
self.xid = 0
def _start_transaction(self):
print('starting transaction', self.xid)
result = self.xid
self.xid = self.xid + 1
return result
def _commit_transaction(self, xid):
print('committing transaction', xid)
def _rollback_transaction(self, xid):
print('rolling back transaction', xid)
# db/transaction.py
import contextlib
class Transaction:
def __init__(self, conn):
self.conn = conn
self.xid = conn._start_connection()
def commit(self):
self.conn._commit_transaction(self.xid)
def rollback(self):
self.conn._rollback_transaction(self.xid)
@contextlib.contextmanager
def start_transaction(connection):
tx = Transaction(connection)
try:
yield tx
except:
tx.rollback()
raise
tx.commit()
# in repl
>>> from db import Connection, start_transaction
>>> conn = Connection()
>>> try:
... with start_transaction(conn) as tx:
... x = 1 + 1
... raise ValueError()
... y = x + 2
... print('transaction 0 =', x, y)
... except ValueError:
... print('Oops! Transaction 0 failed.')
...
#=> starting transaction 0
#=> rolling back transaction 0
#=> Oops! Transaction 0 failed.
>>> try:
... with start_transaction(conn) as tx:
... x = 1 + 1
... y = x + 2
... print('transaction 1 =', x, y)
... except ValueError:
... assert False
...
#=> starting transaction 1
#=> transaction 1 = 2 4
#=> committing transaction 1