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.