Como posso abrir vários arquivos usando "with open" no Python?

671

Quero alterar alguns arquivos de uma só vez, se puder escrever em todos eles. Gostaria de saber se de alguma forma posso combinar as várias chamadas abertas com a withdeclaração:

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

Se isso não for possível, como seria uma solução elegante para esse problema?

Frantischeck003
fonte

Respostas:

1051

No Python 2.7 (ou 3.1, respectivamente), você pode escrever

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

Em versões anteriores do Python, às vezes você pode usar contextlib.nested()para aninhar gerenciadores de contexto. Porém, isso não funcionará como esperado para a abertura de múltiplos arquivos - consulte a documentação vinculada para obter detalhes.


Nos raros casos em que você deseja abrir um número variável de arquivos, tudo ao mesmo tempo, você pode usar contextlib.ExitStack, a partir do Python versão 3.3:

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

Na maioria das vezes, você tem um conjunto variável de arquivos, mas provavelmente os deseja abrir um após o outro.

Sven Marnach
fonte
5
Infelizmente, de acordo com os documentos contextlib.nested, você não deve usá-lo para abrir arquivos: "usar nested () para abrir dois arquivos é um erro de programação, pois o primeiro arquivo não será fechado imediatamente se uma exceção for lançada ao abrir o arquivo segundo arquivo ".
#
41
existe uma maneira de usar withpara abrir uma lista variável de arquivos?
monkut
23
@monkut: pergunta muito boa (você pode realmente fazer isso como uma pergunta separada). Resposta curta: Sim, existe a ExitStackpartir do Python 3.3. Não há maneira fácil de fazer isso em nenhuma versão anterior do Python.
precisa saber é o seguinte
12
É possível que essa sintaxe abranja várias linhas?
tommy.carstensen
9
@ tommy.carstensen: Você pode usar os mecanismos usuais de continuação de linha . Você provavelmente deve usar a continuação da linha de barra invertida para interromper a vírgula, conforme recomendado pelo PEP 9 .
Sven Marnach
99

Basta substituir andpor ,e pronto:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror
Michael
fonte
3
Você deve especificar quais versões do Python suportam essa sintaxe.
Craig McQueen
58

Para abrir muitos arquivos de uma vez ou para caminhos longos, pode ser útil dividir as coisas em várias linhas. Do Python Style Guide, conforme sugerido por @Sven Marnach nos comentários para outra resposta:

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
fonte
1
Com esse recuo, recebo: "flake8: linha de continuação com recuo excessivo para recuo visual"
Louis M
@ LouisisM Isso soa como algo vindo do seu editor ou ambiente, em vez de python base. Se continuar a ser um problema para você, recomendo criar uma nova pergunta relacionada a ela e fornecer mais detalhes sobre seu editor e ambiente.
Michael Ohlrogge
3
Sim, é definitivamente meu editor, e é apenas um aviso. O que eu queria enfatizar é que o seu recuo não está em conformidade com o PEP8. Você deve recuar a segunda abertura () com 8 espaços em vez de alinhá-la com a primeira.
Louis M
2
@LouisM PEP8 é uma diretriz , não regras, e neste caso eu seria certamente ignorá-lo
Nick
2
Sim nenhum problema com isso, pode ser útil para outras pessoas com linters automáticas embora :)
Louis M
14

Aninhado com declarações fará o mesmo trabalho e, na minha opinião, é mais simples de lidar.

Digamos que você tenha inFile.txt e queira gravá-lo em dois outFile's simultaneamente.

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:

Eu não entendo o motivo do voto negativo. Testei meu código antes de publicar minha resposta e funciona como desejado: ele grava em todos os outFile, exatamente como a pergunta. Nenhuma gravação duplicada ou falha na gravação. Por isso, estou realmente curioso para saber por que minha resposta é considerada errada, subótima ou algo assim.

FatihAkici
fonte
1
Eu não sei o que alguém mais votou contra você, mas eu o votei porque esse é o único exemplo que tinha três arquivos (uma entrada, duas saídas) que eram exatamente o que eu precisava.
Adam Michael Wood
2
talvez você esteja com o voto negativo de bcoz em Python> 2.6, você pode escrever mais códigos pitônicos - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (por que não consigo inserir fragmentos de código nos comentários ?!) Estamos em 2018;) versões tão antigas em o passado
El Ruso 29/04
2
Um lembrete amigável para aqueles que fazem cocô em python 2.6: CentOS 6 (que não faz EOL até novembro de 2020), ainda usa py2.6 por padrão. Portanto, essa resposta (no momento) ainda é a melhor IMO geral.
BJ Black
11

Desde o Python 3.3, você pode usar a classe ExitStackdo contextlibmódulo para
abrir com segurança um número arbitrário de arquivos .

Ele pode gerenciar um número dinâmico de objetos sensíveis ao contexto, o que significa que será especialmente útil se você não souber quantos arquivos manipulará .

De fato, o caso de uso canônico mencionado na documentação está gerenciando um número dinâmico de arquivos.

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

Se você estiver interessado nos detalhes, aqui está um exemplo genérico para explicar como 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)

Resultado:

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

Com o python 2.6 Não funcionará, temos que usar abaixo o caminho para abrir vários arquivos:

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

Resposta tardia (8 anos), mas para alguém que deseja juntar vários arquivos em um , a seguinte função pode ser útil:

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
fonte