Como devo ler um arquivo linha por linha no Python?

137

Nos tempos pré-históricos (Python 1.4), fizemos:

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

depois do Python 2.1, fizemos:

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

antes de obtermos o protocolo iterador conveniente no Python 2.3 e poderíamos:

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

Eu vi alguns exemplos usando o mais detalhado:

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

este é o método preferido para a frente?

[edit] Entendo que a instrução with garante o fechamento do arquivo ... mas por que isso não está incluído no protocolo iterador para objetos de arquivo?

thebjorn
fonte
4
imho, a última sugestão não é mais detalhada do que a anterior. Apenas faz mais trabalho (garante que o arquivo seja fechado quando você terminar).
Azhrei 19/07/12
1
@azhrei, é uma linha a mais, então, objetivamente, é mais detalhada.
21712 as
7
Entendi o que você está dizendo, mas estou apenas comparando maçãs com maçãs, a segunda última sugestão da sua postagem também precisa de um código de manipulação de exceções para combinar com o que a última opção faz. Então, na prática, é mais detalhado. Eu acho que depende do contexto qual das duas últimas opções é realmente melhor.
21712 azhrei

Respostas:

227

Há exatamente um motivo pelo qual o seguinte é preferido:

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

Todos nós somos prejudicados pelo esquema relativamente determinístico de contagem de referência do CPython para coleta de lixo. Outras implementações hipotéticas do Python não necessariamente fecham o arquivo "com rapidez suficiente" sem owith bloco se eles usarem algum outro esquema para recuperar a memória.

Em tal implementação, você pode receber um erro "muitos arquivos abertos" no sistema operacional se o seu código abrir arquivos mais rapidamente do que o coletor de lixo chama finalizadores em identificadores de arquivos órfãos. A solução usual é acionar o GC imediatamente, mas esse é um truque desagradável e deve ser feito por todos os funções que possam encontrar o erro, incluindo as das bibliotecas. Que pesadelo.

Ou você pode simplesmente usar o withbloco.

Pergunta bônus

(Pare de ler agora se estiver interessado apenas nos aspectos objetivos da pergunta.)

Por que isso não está incluído no protocolo do iterador para objetos de arquivo?

Esta é uma pergunta subjetiva sobre o design da API, por isso tenho uma resposta subjetiva em duas partes.

No nível interno, isso parece errado, porque faz com que o protocolo do iterador faça duas coisas separadas - iterar sobre linhas e feche o identificador do arquivo - e geralmente é uma má idéia fazer uma função de aparência simples executar duas ações. Nesse caso, parece especialmente ruim porque os iteradores se relacionam de maneira quase funcional e baseada em valores com o conteúdo de um arquivo, mas o gerenciamento de identificadores de arquivo é uma tarefa completamente separada. Esmagar os dois, invisivelmente, em uma ação, é surpreendente para os humanos que lêem o código e torna mais difícil raciocinar sobre o comportamento do programa.

Outras línguas chegaram essencialmente à mesma conclusão. Haskell flertou brevemente com o chamado "IO preguiçoso", que permite iterar sobre um arquivo e fechá-lo automaticamente quando você chegar ao final do fluxo, mas é quase universalmente desencorajado o uso de IO preguiçoso em Haskell atualmente e Haskell os usuários passaram para o gerenciamento de recursos mais explícito, como o Conduit, que se comporta mais como o withbloco do Python.

Em um nível técnico, há algumas coisas que você pode querer fazer com um identificador de arquivo no Python que não funcionaria tão bem se a iteração fechasse o identificador de arquivo. Por exemplo, suponha que eu precise repetir o arquivo duas vezes:

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

Embora este seja um caso de uso menos comum, considere o fato de que talvez eu tenha adicionado as três linhas de código na parte inferior a uma base de código existente que originalmente possuía as três principais linhas. Se a iteração fechou o arquivo, eu não seria capaz de fazer isso. Portanto, manter a iteração e o gerenciamento de recursos separados facilita a composição de trechos de código em um programa Python maior e funcional.

A composição é um dos recursos de usabilidade mais importantes de um idioma ou API.

Dietrich Epp
fonte
1
+1 porque explica o "quando" no meu comentário sobre o op ;-)
azhrei
mesmo com implementação alternativa, o manipulador with só causará problemas para programas que abrem centenas de arquivos em sucessões muito rápidas. A maioria dos programas pode se dar bem com a referência de arquivo dangling sem problemas. A menos que você o desative, eventualmente o GC entrará em ação em algum momento e limpará o identificador do arquivo. withdá a você a tranqüilidade, portanto, ainda é uma prática recomendada.
Lie Ryan
1
@ DietrichEpp: talvez "referência de arquivo dangling" não fosse a palavra certa, eu realmente quis dizer identificadores de arquivo que não estavam mais acessíveis, mas ainda não estavam fechados. De qualquer forma, o GC fechará o identificador de arquivo ao coletar o objeto de arquivo, desde que você não tenha referências extras ao objeto de arquivo e não desative o GC e não abra muitos arquivos rapidamente sucessão, é improvável que você "abra muitos arquivos" por não fechar o arquivo.
Lie Ryan
1
Sim, é exatamente isso que quero dizer com "se seu código abrir arquivos mais rapidamente do que o coletor de lixo chama finalizadores em identificadores de arquivos órfãos".
Dietrich Epp
1
O maior motivo para usar é que, se você não fechar o arquivo, ele não será necessariamente gravado imediatamente.
Antimony
20

Sim,

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

é o caminho a percorrer.

Não é mais detalhado. É mais seguro.

eumiro
fonte
5

se você estiver desativado pela linha extra, poderá usar uma função de invólucro da seguinte forma:

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

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

no Python 3.3, a yield fromdeclaração tornaria isso ainda mais curto:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
Lie Ryan
fonte
2
chamar os xreadlines função .. e colocá-lo em um arquivo chamado xreadlines.py e estamos de volta para Python 2.1 sintaxe :-)
thebjorn
@ thebjorn: talvez, mas o exemplo do Python 2.1 que você citou não estava a salvo de manipulador de arquivos não fechado em implementações alternativas. Uma leitura de arquivo Python 2.1 que esteja protegida contra o manipulador de arquivos não fechado levaria pelo menos 5 linhas.
Lie Ryan
-2
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
fonte
5
Isto realmente não responder à pergunta
Thayne