¿Cómo puedo abrir varios archivos usando "con abrir" en Python?

672

Quiero cambiar un par de archivos a la vez, si puedo escribir en todos ellos. Me pregunto si de alguna manera puedo combinar las múltiples llamadas abiertas con la withdeclaración:

try:
  with open('a', 'w') as a and open('b', 'w') as b:
    do_something()
except IOError as e:
  print 'Operation failed: %s' % e.strerror

Si eso no es posible, ¿cómo sería una solución elegante a este problema?

Frantischeck003
fuente

Respuestas:

1052

A partir de Python 2.7 (o 3.1 respectivamente) puede escribir

with open('a', 'w') as a, open('b', 'w') as b:
    do_something()

En versiones anteriores de Python, a veces puede usar contextlib.nested()para anidar administradores de contexto. Sin embargo, esto no funcionará como se esperaba para abrir archivos múltiples: consulte la documentación vinculada para obtener más detalles.


En el raro caso de que desee abrir un número variable de archivos al mismo tiempo, puede usar contextlib.ExitStack, a partir de la versión 3.3 de Python:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Do something with "files"

Sin embargo, la mayoría de las veces tiene un conjunto variable de archivos, es probable que desee abrirlos uno tras otro.

Sven Marnach
fuente
55
Desafortunadamente, de acuerdo con los documentos contextlib.nested, no debería usarlo para abrir archivos: "usar nested () para abrir dos archivos es un error de programación ya que el primer archivo no se cerrará de inmediato si se produce una excepción al abrir el archivo segundo archivo ".
weronika
41
¿Hay alguna manera de usar withpara abrir una lista variable de archivos?
monkut
23
@monkut: Muy buena pregunta (en realidad podrías hacer esto como una pregunta separada). Respuesta corta: Sí, a ExitStackpartir de Python 3.3. No hay una manera fácil de hacer esto en ninguna versión anterior de Python.
Sven Marnach
12
¿Es posible que esta sintaxis abarque varias líneas?
tommy.carstensen
99
@ tommy.carstensen: puede utilizar los mecanismos habituales de continuación de línea . Probablemente debería usar la continuación de la línea de barra diagonal inversa para romper la coma, como lo recomienda PEP 9 .
Sven Marnach
99

Simplemente reemplace andcon ,y listo:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Miguel
fuente
3
Debe especificar qué versiones de Python admiten esta sintaxis.
Craig McQueen
58

Para abrir muchos archivos a la vez o para rutas de archivo largas, puede ser útil dividir las cosas en varias líneas. De la Guía de estilo de Python sugerida por @Sven Marnach en comentarios a otra respuesta:

with open('/path/to/InFile.ext', 'r') as file_1, \
     open('/path/to/OutFile.ext', 'w') as file_2:
    file_2.write(file_1.read())
Michael Ohlrogge
fuente
1
Con esta sangría obtengo: "flake8: línea de continuación con sangría excesiva para sangría visual"
Louis M
@LouisM Eso suena como algo proveniente de su editor o entorno, en lugar de Python base. Si continúa siendo un problema para usted, recomendaría crear una nueva pregunta relacionada con él y dar más detalles sobre su editor y entorno.
Michael Ohlrogge
3
Sí, definitivamente es mi editor, y es solo una advertencia. Lo que quería enfatizar es que su sangría no cumple con PEP8. Debe sangrar el segundo open () con 8 espacios en lugar de alinearlo con el primero.
Louis M
2
@LouisM PEP8 es una pauta , no reglas, y en este caso lo ignoraría sin duda
Nick
2
Sí, no hay problema con eso, podría ser útil para otras personas con linters automáticos :)
Louis M
15

Anidado con declaraciones hará el mismo trabajo, y en mi opinión, son más fáciles de manejar.

Digamos que tiene inFile.txt y desea escribirlo en dos outFile's simultáneamente.

with open("inFile.txt", 'r') as fr:
    with open("outFile1.txt", 'w') as fw1:
        with open("outFile2.txt", 'w') as fw2:
            for line in fr.readlines():
                fw1.writelines(line)
                fw2.writelines(line)

EDITAR:

No entiendo la razón del voto negativo. Probé mi código antes de publicar mi respuesta, y funciona según lo deseado: escribe en todos los archivos de outFile, tal como lo hace la pregunta. No hay escritura duplicada o no escribir. Así que tengo mucha curiosidad por saber por qué mi respuesta se considera incorrecta, subóptima o algo así.

FatihAkici
fuente
1
No sé qué otra persona te rechazó, pero te VOTÉ porque este es el único ejemplo que tenía tres archivos (una entrada, dos salidas) que resultó ser justo lo que necesitaba.
Adam Michael Wood
2
tal vez te hayan votado mal en Python> 2.6, puedes escribir más código pitónico: gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (¡¿por qué no puedo insertar fragmentos de código en los comentarios ?!) Estamos en 2018;) versiones tan antiguas en el pasado
El Ruso
2
Un recordatorio amistoso para aquellos pyo-poohing python 2.6: CentOS 6 (que no funciona hasta noviembre de 2020), todavía usa py2.6 por defecto. Así que esta respuesta es (a partir de ahora) sigue siendo la mejor IMO en general.
BJ Black
11

Desde Python 3.3, puede usar la clase ExitStackdel contextlibmódulo para
abrir de forma segura un número arbitrario de archivos .

Puede gestionar un número dinámico de objetos sensibles al contexto, lo que significa que resultará especialmente útil. si no sabe cuántos archivos va a manejar .

De hecho, el caso de uso canónico que se menciona en la documentación es administrar 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

Si está interesado en los detalles, aquí hay un ejemplo genérico para explicar cómo ExitStackfunciona:

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(len(stack._exit_callbacks)) # number of callbacks called on exit
    nums = [stack.enter_context(x) for x in xs]
    print(len(stack._exit_callbacks))

print(len(stack._exit_callbacks))
print(nums)

Salida:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]
timgeb
fuente
3

Con python 2.6 no funcionará, tenemos que usar la siguiente forma para abrir múltiples archivos:

with open('a', 'w') as a:
    with open('b', 'w') as b:
Aashutosh jha
fuente
1

Respuesta tardía (8 años), pero para alguien que busca unir múltiples archivos en uno , la siguiente función puede ser de ayuda:

def multi_open(_list):
    out=""
    for x in _list:
        try:
            with open(x) as f:
                out+=f.read()
        except:
            pass
            # print(f"Cannot open file {x}")
    return(out)

fl = ["C:/bdlog.txt", "C:/Jts/tws.vmoptions", "C:/not.exist"]
print(multi_open(fl))

2018-10-23 19:18:11.361 PROFILE  [Stop Drivers] [1ms]
2018-10-23 19:18:11.361 PROFILE  [Parental uninit] [0ms]
...
# This file contains VM parameters for Trader Workstation.
# Each parameter should be defined in a separate line and the
...
CONvid19
fuente