with statement manager espaƱol context python python-3.x contextmanager

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:

  1. __init__ (asignación de la clase)
  2. __enter__ (entrar en contexto)
  3. __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.