¿Múltiples variables en una declaración 'con'?

Resuelto pufferfish asked hace 15 años • 8 respuestas

¿ Es posible declarar más de una variable usando una withdeclaración en Python?

Algo como:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

... ¿o el problema es limpiar dos recursos al mismo tiempo?

pufferfish avatar May 21 '09 21:05 pufferfish
Aceptado

Es posible en Python 3 desde v3.1 y Python 2.7 . La nueva withsintaxis admite múltiples administradores de contexto:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

A diferencia de contextlib.nested, esto garantiza que ase bllamará a __exit__()and incluso si C()su __enter__()método genera una excepción.

También puede utilizar variables anteriores en definiciones posteriores (h/t Ahmad a continuación):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

A partir de Python 3.10, puedes usar paréntesis :

with (
    A() as a, 
    B(a) as b, 
    C(a, b) as c,
):
    doSomething(a, c)
Rafał Dowgird avatar Jul 02 '2009 11:07 Rafał Dowgird

Tenga en cuenta que si divide las variables en líneas, antes de Python 3.10 debía usar barras invertidas para ajustar las nuevas líneas.

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

Los paréntesis no funcionan, ya que Python crea una tupla.

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

Como las tuplas carecen de un __enter__atributo, aparece un error (no descriptivo y no identifica el tipo de clase):

AttributeError: __enter__

Si intenta utilizar asentre paréntesis, Python detecta el error en el momento del análisis:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)
SyntaxError: invalid syntax

¿Cuándo se va a arreglar esto?

Este problema se rastrea en https://bugs.python.org/issue12782 .

Python anunció en PEP 617 que reemplazarían el analizador original por uno nuevo. Debido a que el analizador original de Python es LL(1), no puede distinguir entre "administradores de contexto múltiples" with (A(), B()):y "tupla de valores" with (A(), B())[0]:.

El nuevo analizador puede analizar correctamente varios administradores de contexto entre paréntesis. El nuevo analizador se ha habilitado en 3.9. Se informó que esta sintaxis seguirá siendo rechazada hasta que se elimine el analizador antiguo en Python 3.10, y este cambio de sintaxis se informó en las notas de la versión 3.10 . Pero en mis pruebas, también funciona en Python 3.9.6 de trinket.io.

nyanpasu64 avatar Jun 20 '2018 23:06 nyanpasu64

Desde Python 3.3, puedes usar la clase ExitStackdel contextlibmódulo.

Puede gestionar una cantidad dinámica de objetos contextuales, lo que significa que resultará especialmente útil si no sabes cuántos archivos vas a manejar.

El caso de uso canónico que se menciona en la documentación es la gestión de una cantidad dinámica de archivos.

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

Aquí hay un ejemplo genérico:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

Producción:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]
timgeb avatar May 10 '2018 06:05 timgeb

Creo que quieres hacer esto en su lugar:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)
Andrew Hare avatar May 21 '2009 14:05 Andrew Hare

Desde Python 3.10 hay una nueva característica de administradores de contexto entre paréntesis , que permite sintaxis como:

with (
    A() as a,
    B() as b
):
    do_something(a, b)
Chris_Rands avatar Feb 26 '2021 15:02 Chris_Rands