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

391

¿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 es limpiar dos recursos al mismo tiempo el problema?

pez globo
fuente
Tal vez así: con [expr1, expr2] como f: y luego use f [0] yf [1].
jbasko
Hubiera sido bueno porque no es necesario importar algo ... pero no funciona AttributeError: el objeto 'list' no tiene el atributo ' exit '
pufferfish
Si Python acabara de cerrar, no necesitaría la declaración with
BT
No necesitas usar una declaración with, ¿verdad? Puede establecer file_out y file_in en None, luego hacer un try / except / finalmente donde los abra y procesarlos en el try, y luego finalmente ciérrelos si no son None. No se necesita doble sangría para eso.
M Katz
1
Muchas de estas respuestas no abordan la necesidad de más de dos con declaraciones. Teóricamente puede haber aplicaciones que necesiten abrir decenas de contextos, la anidación se desmorona muy rápidamente si se imponen limitaciones de longitud de línea.
ThorSummoner

Respuestas:

667

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 la contextlib.nested, esto garantiza que ay btendrán su __exit__()'llama incluso si C()o no lo __enter__()método lanza una excepción.

También puede usar 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)
Rafał Dowgird
fuente
1
¿Es posible establecer campos iguales a algo en la declaración como en with open('./file') as arg.x = file:?
Charlie Parker
13
Además, es posible: con A () como a, B (a) como b, C (a, b) como c:
Ahmad Yoosofan
prueba de clase2: x = 1; t2 = test2 () con open ('f2.txt') como t2.x: para l1 en t2.x.readlines (): print (l1); # Charlie Parker # probado en python 3.6
Ahmad Yoosofan
1
Tenga en cuenta que ases opcional.
Sławomir Lenart
para aclarar lo que dice @ SławomirLenart: asse requiere si necesita el objeto ao b, pero no se requiere todo as ao as bno
Ciprian Tomoiagă
56

contextlib.nested apoya esto:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

Actualización:
para citar la documentación, con respecto a contextlib.nested:

En desuso desde la versión 2.7 : la declaración with ahora admite esta funcionalidad directamente (sin los caprichos confusos propensos a errores).

Vea la respuesta de Rafał Dowgird para más información.

Alex Martelli
fuente
34
Lamento decir eso, pero creo que el nestedadministrador de contexto es un error y nunca debe usarse. En este ejemplo, si abrir el segundo archivo genera una excepción, el primer archivo no se cerrará en absoluto, lo que destruirá totalmente el propósito de usar administradores de contexto.
Rafał Dowgird
¿Por qué dices eso? La documentación dice que usar anidado es equivalente a anidado 'con'
James Hopkin
@ Rafal: un vistazo al manual parece indicar que Python anida correctamente las declaraciones con. El verdadero problema es si el segundo archivo arroja una excepción al cerrar.
Desconocido
10
@James: No, el código equivalente en los documentos en docs.python.org/library/contextlib.html#contextlib.nested difiere de los withbloques anidados estándar . Los administradores se crean en orden antes de ingresar los bloques with: m1, m2, m3 = A (), B (), C () Si B () o C () falla con excepción, entonces su única esperanza de finalizar correctamente A ( ) es el recolector de basura.
Rafał Dowgird
8
En desuso desde la versión 2.7 . Nota: La declaración with ahora admite esta funcionalidad directamente (sin las peculiaridades confusas propensas a errores).
miku
36

Tenga en cuenta que si divide las variables en líneas, debe 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 en su lugar.

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

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

AttributeError: __enter__

Si intenta utilizarlo 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)

Error de sintaxis: sintaxis invalida

https://bugs.python.org/issue12782 parece estar relacionado con este problema.

nyanpasu64
fuente
16

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
fuente
55
Así es como actualmente lo hago, pero entonces la anidación es dos veces tan profundo como yo quiero (media) que sea ...
pez globo
Sin embargo, creo que este es el enfoque más limpio: cualquier otro enfoque será más difícil de leer. La respuesta de Alex Martelli parece estar más cerca de lo que quieres, pero es mucho menos legible. ¿Por qué es tan importante anidar?
Andrew Hare
77
No es gran cosa, es cierto, pero, por "importar esto" (también conocido como "Zen de Python"), "plano es mejor que anidado", es por eso que agregamos contextlib.nested a la biblioteca estándar. Por cierto, 3.1 podría tener una nueva sintaxis "con A () como a, B () como b:" (el parche está en, sin embargo, no hay pronunciamiento de BDFL al respecto hasta ahora) para un soporte más directo (así que claramente la solución de la biblioteca no es ' t considerado perfecto ... pero evitar el anidamiento no deseado es definitivamente un objetivo ampliamente compartido entre los desarrolladores principales de Python).
Alex Martelli
2
@Alex: Muy cierto, pero también debemos considerar que "la legibilidad cuenta".
Andrew Hare
44
@ Andrew: Creo que un nivel de sangría expresa mejor la lógica prevista del programa, que es crear "atómicamente" dos variables y limpiarlas más tarde juntas (me doy cuenta de que esto no es realmente lo que sucede). Que el tema excepción es un tema de oferta, aunque
el pez globo
12

Desde Python 3.3, puede usar la clase ExitStackdesde el contextlibmódulo.

Puede administrar una cantidad dinámica de objetos sensibles al contexto, lo que significa que resultará especialmente útil si no sabe cuántos archivos va a manejar.

El caso de uso canónico que se menciona en la documentación es administrar un número dinámico 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)

Salida:

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
fuente
0

En Python 3.1+ puede especificar múltiples expresiones de contexto, y se procesarán como si se anidaran varias withdeclaraciones:

with A() as a, B() as b:
    suite

es equivalente a

with A() as a:
    with B() as b:
        suite

Esto también significa que puede usar el alias de la primera expresión en la segunda (útil cuando se trabaja con conexiones / cursores db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
Eugene Yarmash
fuente