python - manager - Alternativa a contextlib.nested con número variable de administradores de contexto
context manager python (4)
Es un poco desconcertante que los mantenedores de python3 decidieron romper la compatibilidad hacia atrás, ya que la implementación nested
en términos de ExitStack
es bastante sencilla:
try:
from contextlib import nested # Python 2
except ImportError:
from contextlib import ExitStack, contextmanager
@contextmanager
def nested(*contexts):
"""
Reimplementation of nested in python 3.
"""
with ExitStack() as stack:
for ctx in contexts:
stack.enter_context(ctx)
yield contexts
Tenemos un código que invoca un número variable de administradores de contexto en función de los parámetros de tiempo de ejecución:
from contextlib import nested, contextmanager
@contextmanager
def my_context(arg):
print("entering", arg)
try:
yield arg
finally:
print("exiting", arg)
def my_fn(items):
with nested(*(my_context(arg) for arg in items)) as managers:
print("processing under", managers)
my_fn(range(3))
Sin embargo, contextlib.nested
está en desuso desde Python 2.7 :
DeprecationWarning: With-statements now directly support multiple context managers
Las respuestas a Múltiples variables en Python ''con'' indican que contextlib.nested
tiene algunos "errores confusos propensos a errores", pero la alternativa sugerida de usar el administrador múltiple with
declaración no funcionará para un número variable de administradores de contexto (y también rompe la compatibilidad hacia atrás).
¿Hay alternativas a contextlib.nested
que no estén en desuso y (preferiblemente) no tengan los mismos errores?
¿O debería seguir usando contextlib.nested
e ignorar la advertencia? Si es así, ¿debería planear que contextlib.nested
se elimine en algún momento en el futuro?
La nueva contextlib.ExitStack se agregó como un reemplazo para contextlib.nested()
(ver bugs.python.org/issue13585 ).
Está codificado de tal manera que puede usarlo directamente en Python 2:
import sys
from collections import deque
class ExitStack(object):
"""Context manager for dynamic management of a stack of exit callbacks
For example:
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
# All opened files will automatically be closed at the end of
# the with statement, even if attempts to open files later
# in the list raise an exception
"""
def __init__(self):
self._exit_callbacks = deque()
def pop_all(self):
"""Preserve the context stack by transferring it to a new instance"""
new_stack = type(self)()
new_stack._exit_callbacks = self._exit_callbacks
self._exit_callbacks = deque()
return new_stack
def _push_cm_exit(self, cm, cm_exit):
"""Helper to correctly register callbacks to __exit__ methods"""
def _exit_wrapper(*exc_details):
return cm_exit(cm, *exc_details)
_exit_wrapper.__self__ = cm
self.push(_exit_wrapper)
def push(self, exit):
"""Registers a callback with the standard __exit__ method signature
Can suppress exceptions the same way __exit__ methods can.
Also accepts any object with an __exit__ method (registering a call
to the method instead of the object itself)
"""
# We use an unbound method rather than a bound method to follow
# the standard lookup behaviour for special methods
_cb_type = type(exit)
try:
exit_method = _cb_type.__exit__
except AttributeError:
# Not a context manager, so assume its a callable
self._exit_callbacks.append(exit)
else:
self._push_cm_exit(exit, exit_method)
return exit # Allow use as a decorator
def callback(self, callback, *args, **kwds):
"""Registers an arbitrary callback and arguments.
Cannot suppress exceptions.
"""
def _exit_wrapper(exc_type, exc, tb):
callback(*args, **kwds)
# We changed the signature, so using @wraps is not appropriate, but
# setting __wrapped__ may still help with introspection
_exit_wrapper.__wrapped__ = callback
self.push(_exit_wrapper)
return callback # Allow use as a decorator
def enter_context(self, cm):
"""Enters the supplied context manager
If successful, also pushes its __exit__ method as a callback and
returns the result of the __enter__ method.
"""
# We look up the special methods on the type to match the with statement
_cm_type = type(cm)
_exit = _cm_type.__exit__
result = _cm_type.__enter__(cm)
self._push_cm_exit(cm, _exit)
return result
def close(self):
"""Immediately unwind the context stack"""
self.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, *exc_details):
# We manipulate the exception state so it behaves as though
# we were actually nesting multiple with statements
frame_exc = sys.exc_info()[1]
def _fix_exception_context(new_exc, old_exc):
while 1:
exc_context = new_exc.__context__
if exc_context in (None, frame_exc):
break
new_exc = exc_context
new_exc.__context__ = old_exc
# Callbacks are invoked in LIFO order to match the behaviour of
# nested context managers
suppressed_exc = False
while self._exit_callbacks:
cb = self._exit_callbacks.pop()
try:
if cb(*exc_details):
suppressed_exc = True
exc_details = (None, None, None)
except:
new_exc_details = sys.exc_info()
# simulate the stack of exceptions by setting the context
_fix_exception_context(new_exc_details[1], exc_details[1])
if not self._exit_callbacks:
raise
exc_details = new_exc_details
return suppressed_exc
Use esto como su administrador de contexto, luego agregue administradores de contexto anidados a voluntad:
with ExitStack() as stack:
managers = [stack.enter_context(my_context(arg)) for arg in items]
print("processing under", managers)
Para su administrador de contexto de ejemplo, esto imprime:
>>> my_fn(range(3))
(''entering'', 0)
(''entering'', 1)
(''entering'', 2)
(''processing under'', [0, 1, 2])
(''exiting'', 2)
(''exiting'', 1)
(''exiting'', 0)
También puede instalar el módulo contextlib2
; incluye ExitStack
como backport.
Parece que se supone que debes usar la instrucción with con múltiples instancias del administrador de contexto.
de https://pymotw.com/2/contextlib/ :
En Python 2.7 y versiones posteriores, nested () está en desuso porque la instrucción with admite el anidamiento directamente.
import contextlib
@contextlib.contextmanager
def make_context(name):
print ''entering:'', name
yield name
print ''exiting :'', name
with make_context(''A'') as A, make_context(''B'') as B, make_context(''C'') as C:
print ''inside with statement:'', A, B, C
import sys
import contextlib
class nodeA(object):
def __init__(self):
print( ''__init__ nodeA'')
def __enter__(self):
print( ''__enter__ nodeA'')
def __exit__(self, a, b, c):
print( ''__exit__ nodeA'')
class nodeB(object):
def __init__(self):
print( ''__init__ nodeB'')
def __enter__(self):
print( ''__enter__ nodeB'')
def __exit__(self, a, b, c):
print( ''__exit__ nodeB'')
class nodeC(object):
def __init__(self):
print( ''__init__ nodeC'')
def __enter__(self):
print( ''__enter__ nodeC'')
def __exit__(self, a, b, c):
print( ''__exit__ nodeC'')
print( ''Start...'')
a = nodeA()
b = nodeB()
c = nodeC()
print( ''Python version: %s'' % (sys.version))
if sys.version.startswith(''2''):
print(''Use python 2!'')
with contextlib.nested(a, b, c):
print(''hallo?'')
if sys.version.startswith(''3''):
print(''Use python 3!'')
with contextlib.ExitStack() as stack:
[stack.enter_context(arg) for arg in [a,b,c]]
print(''...end!'')