Objects designed to be used in a with statement:
with context_manager:
# do stuff
passwith 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:
bodywith 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__()LoggingContextManagerclass 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 itcontextlibwith statement"contextlib.contextmanagerimport 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:
BODYimport 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