statement - with python 3
__init__ vs__enter__ en administradores de contexto (2)
Según tengo entendido, los __init__()
y __enter__()
del administrador de contexto se llaman exactamente uno por uno, uno tras otro, sin dejar ninguna posibilidad de que se ejecute ningún otro código en medio. ¿Cuál es el propósito de separarlos en dos métodos y qué debo poner en cada uno?
Edit: lo siento, no estaba prestando atención a los documentos.
Edición 2: en realidad, la razón por la que me confundí es porque estaba pensando en el decorador de @contextmanager
. Un administrador de contexto creado con @contextmananger
solo se puede usar una vez (el generador se agotará después del primer uso), por lo que a menudo se escriben con la llamada del constructor dentro with
declaración; y si esa fuera la única forma de usarlo with
declaración, mi pregunta hubiera tenido sentido. Por supuesto, en realidad, los gestores de contexto son más generales de lo que @contextmanager
puede crear; en particular, los gerentes de contexto pueden, en general, ser reutilizados. Espero haberlo hecho bien esta vez?
Según tengo entendido, los
__init__()
y__enter__()
del administrador de contexto se llaman exactamente uno por uno, uno tras otro, sin dejar ninguna posibilidad de que se ejecute ningún otro código en medio.
Y tu entendimiento es incorrecto. __init__
se llama cuando se crea el objeto, __enter__
cuando se ingresa con with
sentencia, y estas son 2 cosas bastante distintas. A menudo es para que se llame directamente al constructor with
inicialización, sin un código intermedio, pero esto no tiene por qué ser el caso.
Considera este ejemplo:
class Foo:
def __init__(self):
print(''__init__ called'')
def __enter__(self):
print(''__enter__ called'')
return self
def __exit__(self, *a):
print(''__exit__ called'')
myobj = Foo()
print(''/nabout to enter with 1'')
with myobj:
print(''in with 1'')
print(''/nabout to enter with 2'')
with myobj:
print(''in with 2'')
myobj
puede inicializarse por separado e ingresarse en múltiplos with
bloques:
Salida:
__init__ called
about to enter with 1
__enter__ called
in with 1
__exit__ called
about to enter with 2
__enter__ called
in with 2
__exit__ called
Además, si __init__
y __enter__
no estuvieran separados, ni siquiera sería posible usar lo siguiente:
def open_etc_file(name):
return open(os.path.join(''/etc'', name))
with open_etc_file(''passwd''):
...
ya que la inicialización (dentro de open
) está claramente separada de with
entrada.
Los gestores creados por contextlib.manager
son de un solo participante, pero nuevamente pueden construirse fuera del bloque with
. Tomemos el ejemplo:
from contextlib import contextmanager
@contextmanager
def tag(name):
print("<%s>" % name)
yield
print("</%s>" % name)
Puedes usar esto como:
def heading(level=1):
return tag(''h{}''.format(level))
my_heading = heading()
print(''Below be my heading'')
with my_heading:
print(''Here be dragons'')
salida:
Below be my heading
<h1>
Here be dragons
</h1>
Sin embargo, si intenta reutilizar my_heading
(y, en consecuencia, tag
), obtendrá
RuntimeError: generator didn''t yield
La respuesta de Antti Haapalas está perfectamente bien. Solo quería elaborar un poco sobre el uso de argumentos (como myClass(* args)
), ya que eso no me quedó claro (retrospectiva, me pregunto por qué ...)
Usar argumentos para inicializar tu clase en una declaración with
no es diferente de usar la clase de la manera habitual. Las llamadas se realizarán en el siguiente orden:
-
__init__
(asignación de la clase) -
__enter__
(entrar en contexto) -
__exit__
(dejando el contexto)
Ejemplo simple:
class Foo:
def __init__(self, i):
print(''__init__ called: {}''.format(i))
self.i = i
def __enter__(self):
print(''__enter__ called'')
return self
def do_something(self):
print(''do something with {}''.format(self.i))
def __exit__(self, *a):
print(''__exit__ called'')
with Foo(42) as bar:
bar.do_something()
Salida:
__init__ called: 42
__enter__ called
do something with 42
__exit__ called
Si desea asegurarse de que sus llamadas puedan (casi) solo ser usadas en un contexto (por ejemplo, para forzar la llamada a __exit__
), consulte la publicación de here . En los comentarios también encontrará una respuesta a la pregunta sobre cómo usar los argumentos incluso en ese momento.