Come posso aprire più file usando "with open" in Python?


672

Voglio cambiare un paio di file contemporaneamente, se posso scriverli tutti. Mi chiedo se in qualche modo posso combinare le molteplici chiamate aperte con la withdichiarazione:

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 ciò non fosse possibile, quale sarebbe una soluzione elegante a questo problema?

Risposte:


1052

A partire da Python 2.7 (o 3.1 rispettivamente) puoi scrivere

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

Nelle versioni precedenti di Python, a volte è possibile utilizzare contextlib.nested()per nidificare i gestori di contesto. Tuttavia, ciò non funzionerà come previsto per l'apertura di file multipli. Per i dettagli, consultare la documentazione collegata.


Nel raro caso in cui si desideri aprire contemporaneamente un numero variabile di file, è possibile utilizzare contextlib.ExitStack, a partire da Python versione 3.3:

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

La maggior parte delle volte hai un set variabile di file, probabilmente vorrai aprirli uno dopo l'altro.


5
Sfortunatamente, secondo i documenti contextlib.nested, non dovresti usarlo per l'apertura dei file: "l'utilizzo di nested () per aprire due file è un errore di programmazione poiché il primo file non verrà chiuso prontamente se viene generata un'eccezione all'apertura del file secondo file ".
weronika,

41
c'è un modo per usare withper aprire un elenco variabile di file?
monkut,

23
@monkut: Ottima domanda (potresti effettivamente fare questa domanda separatamente). Risposta breve: Sì, a ExitStackpartire da Python 3.3. Non esiste un modo semplice per farlo in nessuna versione precedente di Python.
Sven Marnach,

12
È possibile che questa sintassi si estenda su più righe?
tommy.carstensen,

9
@ tommy.carstensen: è possibile utilizzare i soliti meccanismi di continuazione della linea . Probabilmente dovresti usare la continuazione della riga di barra rovesciata per interrompere la virgola, come raccomandato da PEP 9 .
Sven Marnach,

99

Basta sostituire andcon ,e il gioco è fatto:

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

3
È necessario specificare quali versioni di Python supportano questa sintassi.
Craig McQueen,

58

Per l'apertura di più file contemporaneamente o per percorsi di file lunghi, può essere utile suddividere le cose su più righe. Dalla Python Style Guide come suggerito da @Sven Marnach nei commenti ad un'altra risposta:

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())

1
Con questo rientro ottengo: "flake8: linea di continuazione troppo indentata per rientro visivo"
Louis M

@LouisM Sembra qualcosa proveniente dal tuo editor o ambiente, piuttosto che base Python. Se il problema persiste, ti consiglio di creare una nuova domanda relativa e di fornire maggiori dettagli sul tuo editor e ambiente.
Michael Ohlrogge,

3
Sì, è sicuramente il mio editore, ed è solo un avvertimento. Quello che volevo sottolineare è che il tuo rientro non è conforme a PEP8. Dovresti rientrare nel secondo open () con 8 spazi invece di allinearlo con il primo.
Louis M,

2
@LouisM PEP8 è una linea guida , non regole, e in questo caso sicuramente lo ignorerei
Nick,

2
Sì, nessun problema, potrebbe essere utile per altre persone con linters automatici :)
Louis M

15

Nidificato con le dichiarazioni farà lo stesso lavoro e, secondo me, è più semplice da gestire.

Supponiamo che tu abbia inFile.txt e desideri scriverlo contemporaneamente in due file outFile.

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)

MODIFICARE:

Non capisco il motivo del downvote. Ho testato il mio codice prima di pubblicare la mia risposta e funziona come desiderato: scrive su tutti i file outFile, proprio come fa la domanda. Nessuna scrittura duplicata o mancata scrittura. Quindi sono davvero curioso di sapere perché la mia risposta è considerata sbagliata, non ottimale o qualcosa del genere.


1
non so cosa qualcun altro ti abbia sottovalutato, ma ti ho votato perché questo è l'unico esempio che ha avuto tre file (un input, due output) che sono risultati esattamente ciò di cui avevo bisogno.
Adam Michael Wood, il

2
forse sei sottovalutato bcoz in Python> 2.6 puoi scrivere più codice pythonic - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (perché non posso inserire frammento di codice nei commenti ?!) Siamo nel 2018;) versioni così antiche il passato
El Ruso,

2
Un amichevole promemoria per quei python 2.6 che fanno la cacca: CentOS 6 (che non è EOL fino al novembre 2020), usa ancora py2.6 per impostazione predefinita. Quindi questa risposta è (al momento) ancora la migliore IMO generale.
BJ Black,

11

Da Python 3.3, puoi usare la classe ExitStackdal contextlibmodulo per
aprire in sicurezza un numero arbitrario di file .

Può gestire un numero dinamico di oggetti sensibili al contesto, il che significa che si rivelerà particolarmente utile se non sai quanti file gestirai .

In effetti, il caso d'uso canonico menzionato nella documentazione gestisce un numero dinamico di file.

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 sei interessato ai dettagli, ecco un esempio generico per spiegare come ExitStackfunziona:

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)

Produzione:

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

3

Con Python 2.6 non funzionerà, dobbiamo usare il modo seguente per aprire più file:

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

1

Risposta tardiva (8 anni), ma per chi cerca di unire più file in uno solo , può essere utile la seguente funzione:

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
...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.