Come devo leggere un file riga per riga in Python?


137

In tempi preistorici (Python 1.4) abbiamo fatto:

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

dopo Python 2.1, abbiamo fatto:

for line in open('filename.txt').xreadlines():
    print line

prima di ottenere il comodo protocollo iteratore in Python 2.3 e potevamo fare:

for line in open('filename.txt'):
    print line

Ho visto alcuni esempi usando il più dettagliato:

with open('filename.txt') as fp:
    for line in fp:
        print line

è questo il metodo preferito che va avanti?

[modifica] Ho capito che l'istruzione with assicura la chiusura del file ... ma perché non è incluso nel protocollo iteratore per gli oggetti file?


4
imho, l'ultimo suggerimento non è più dettagliato di quello precedente. Fa solo più lavoro (assicura che il file venga chiuso al termine).
Azhrei,

1
@azhrei è una riga in più, quindi oggettivamente è più dettagliata.
thebjorn,

7
Ho capito cosa stai dicendo, ma sto solo dicendo di confrontare le mele con le mele, il secondo ultimo suggerimento nel tuo post ha bisogno anche del codice di gestione delle eccezioni per abbinare ciò che fa l'ultima opzione. Quindi in pratica è più prolisso. Immagino che dipenda dal contesto quale delle ultime due opzioni sia la migliore, davvero.
Azhrei,

Risposte:


227

C'è esattamente un motivo per cui si preferisce quanto segue:

with open('filename.txt') as fp:
    for line in fp:
        print line

Siamo tutti viziati dallo schema relativamente deterministico di conteggio dei riferimenti di CPython per la garbage collection. Altre ipotetiche implementazioni di Python non chiuderanno necessariamente il file "abbastanza rapidamente" senza il withblocco se usano qualche altro schema per recuperare la memoria.

In tale implementazione, potresti ottenere un errore "troppi file aperti" dal sistema operativo se il tuo codice apre i file più velocemente di quanto il garbage collector chiama finalizzatori su handle di file orfani. La solita soluzione consiste nell'attivare immediatamente il GC, ma si tratta di un brutto trucco e deve essere eseguito da ogni funzione che potrebbe riscontrare l'errore, compresi quelli nelle librerie. Che incubo.

O potresti semplicemente usare il withblocco.

Domanda bonus

(Smetti di leggere ora se sei interessato solo agli aspetti oggettivi della domanda.)

Perché non è incluso nel protocollo iteratore per gli oggetti file?

Questa è una domanda soggettiva sulla progettazione dell'API, quindi ho una risposta soggettiva in due parti.

A livello intestinale, questo sembra sbagliato, perché fa sì che il protocollo iteratore faccia due cose separate — itera su linee e chiude l'handle del file — ed è spesso una cattiva idea fare una funzione dall'aspetto semplice fare due azioni. In questo caso, ci si sente particolarmente male perché gli iteratori si relazionano in modo quasi funzionale e basato sul valore al contenuto di un file, ma la gestione degli handle di file è un'attività completamente separata. Schiacciare entrambi, invisibilmente, in una sola azione, è sorprendente per gli umani che leggono il codice e rende più difficile ragionare sul comportamento del programma.

Altre lingue sono sostanzialmente arrivate alla stessa conclusione. Haskell flirta brevemente con il cosiddetto "IO pigro" che ti consente di scorrere su un file e di chiuderlo automaticamente quando arrivi alla fine del flusso, ma è quasi universalmente scoraggiato usare l'IO pigro in Haskell in questi giorni, e Haskell gli utenti sono passati principalmente a una gestione delle risorse più esplicita come Conduit, che si comporta più come il withblocco in Python.

A livello tecnico, ci sono alcune cose che potresti voler fare con un handle di file in Python che non funzionerebbe altrettanto bene se l'iterazione chiudesse l'handle di file. Ad esempio, supponiamo di dover ripetere due volte il file:

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

Mentre questo è un caso d'uso meno comune, considera il fatto che avrei potuto semplicemente aggiungere le tre righe di codice in fondo a una base di codice esistente che originariamente aveva le prime tre righe. Se l'iterazione chiudesse il file, non sarei in grado di farlo. Pertanto, mantenere l'iterazione e la gestione delle risorse separate semplifica la composizione di blocchi di codice in un programma Python più grande e funzionante.

La componibilità è una delle caratteristiche di usabilità più importanti di una lingua o API.


1
+1 perché spiega il "quando" nel mio commento
sull'op

anche con un'implementazione alternativa, il gestore with causerà solo problemi per i programmi che aprono centinaia di file in successioni molto rapide. La maggior parte dei programmi può cavarsela con il riferimento al file pendente senza problemi. A meno che non lo si disabiliti, alla fine il GC si avvierà a volte e cancellerà l'handle del file. withti dà la tranquillità, quindi è ancora una buona pratica.
Lie Ryan,

1
@DietrichEpp: forse "riferimento a file penzolanti" non erano le parole giuste, intendevo davvero maniglie di file che non erano più accessibili ma non ancora chiuse. In ogni caso, il GC chiuderà l'handle del file quando raccoglie l'oggetto file, quindi finché non si hanno riferimenti extra all'oggetto file e non si disabilita GC e non si aprono molti file in modo rapido successione, è improbabile che si ottengano "troppi file aperti" a causa della mancata chiusura del file.
Lie Ryan,

1
Sì, questo è esattamente ciò che intendo con "se il tuo codice apre i file più velocemente di quanto il garbage collector chiama i finalizzatori su handle di file orfani".
Dietrich Epp,

1
Il motivo principale da utilizzare è che se non si chiude il file, non verrà necessariamente scritto immediatamente.
Antimonio

20

Sì,

with open('filename.txt') as fp:
    for line in fp:
        print line

è la strada da percorrere.

Non è più prolisso. È più sicuro.


5

se la linea aggiuntiva viene disattivata, puoi utilizzare una funzione wrapper in questo modo:

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

in Python 3.3, l' yield fromaffermazione renderebbe questo ancora più breve:

def with_iter(iterable):
    with iterable as iter:
        yield from iter

2
chiama la funzione xreadlines .. e inseriscilo in un file chiamato xreadlines.py e torniamo alla sintassi di Python 2.1 :-)
thebjorn

@thebjorn: forse, ma l'esempio di Python 2.1 che hai citato non era sicuro dal gestore di file non chiuso in implementazioni alternative. Una lettura di file Python 2.1 che è sicura dal gestore di file non chiuso richiederebbe almeno 5 righe.
Lie Ryan,

-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()

5
Questo non risponde davvero alla domanda
Thayne
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.