A leitura de um arquivo inteiro deixa o identificador de arquivo aberto?

372

Se você ler um arquivo inteiro com, content = open('Path/to/file', 'r').read()o identificador de arquivo fica aberto até o script sair? Existe um método mais conciso para ler um arquivo inteiro?

tMC
fonte

Respostas:

585

A resposta para essa pergunta depende um pouco da implementação específica do Python.

Para entender do que se trata, preste atenção especial ao fileobjeto real . No seu código, esse objeto é mencionado apenas uma vez, em uma expressão, e fica inacessível imediatamente após o read()retorno da chamada.

Isso significa que o objeto do arquivo é lixo. A única pergunta restante é "Quando o coletor de lixo coletará o objeto de arquivo?".

no CPython, que usa um contador de referência, esse tipo de lixo é percebido imediatamente e, portanto, será coletado imediatamente. Isso geralmente não é verdade para outras implementações python.

Uma solução melhor, para garantir que o arquivo esteja fechado, é este padrão:

with open('Path/to/file', 'r') as content_file:
    content = content_file.read()

que sempre fechará o arquivo imediatamente após o término do bloco; mesmo se ocorrer uma exceção.

Edit: Para colocar um ponto mais fino nele:

Diferente de file.__exit__(), que é "automaticamente" chamado em uma withconfiguração de gerenciador de contexto, a única outra maneira que file.close()é automaticamente chamada (ou seja, que não seja explicitamente chamada por si mesmo) é via file.__del__(). Isso nos leva à questão de quando é __del__()chamado?

Um programa gravado corretamente não pode assumir que os finalizadores serão executados a qualquer momento antes do encerramento do programa.

- https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13203

Em particular:

Os objetos nunca são explicitamente destruídos; no entanto, quando se tornam inacessíveis, podem ser coletados no lixo. É permitido que uma implementação adie a coleta de lixo ou a omita por completo - é uma questão de qualidade da implementação como a coleta de lixo é implementada, desde que nenhum objeto seja coletado que ainda esteja acessível.

[...]

Atualmente, o CPython usa um esquema de contagem de referência com detecção atrasada (opcional) de lixo vinculado ciclicamente, que coleta a maioria dos objetos assim que eles se tornam inacessíveis, mas não é garantido coletar lixo contendo referências circulares.

- https://docs.python.org/3.5/reference/datamodel.html#objects-values-and-types

(Ênfase minha)

mas, como sugere, outras implementações podem ter outro comportamento. Como exemplo, o PyPy possui 6 implementações diferentes de coleta de lixo !

SingleNegationElimination
fonte
24
Por um tempo, não houve realmente outras implementações do Python; mas confiar nos detalhes da implementação não é realmente Pythonic.
Karl Knechtel
Ainda é específico da implementação ou já foi padronizado? Não ligar __exit__()nesses casos parece uma falha de design.
RR
2
@jgmjgm É justamente por causa desses três problemas, o GC ser imprevisível try/ finallycomplicado e a inutilidade altamente comum dos manipuladores de limpeza que withresolvem. A diferença entre "fechar explicitamente" e "gerenciar com with" é que o manipulador de saída é chamado mesmo se uma exceção for lançada. Você pode incluir close()uma finallycláusula, mas isso não é muito diferente de usar with, um pouco mais confuso (3 linhas extras em vez de 1) e um pouco mais difícil de acertar.
SingleNegationElimination
11
O que eu não entendo é por que 'with' seria mais confiável, pois também não é explícito. É porque a especificação diz que precisa fazer isso sempre implementada dessa maneira?
Jgmjgm
3
@jgmjgm é porque mais confiável with foo() as f: [...]é basicamente o mesmo que f = foo(), f.__enter__()[...] e f.__exit__() com exceções manipuladas , de modo que __exit__é sempre chamado. Portanto, o arquivo sempre é fechado.
neingeist
104

Você pode usar o pathlib .

Para Python 3.5 e superior:

from pathlib import Path
contents = Path(file_path).read_text()

Para versões mais antigas do Python, use pathlib2 :

$ pip install pathlib2

Então:

from pathlib2 import Path
contents = Path(file_path).read_text()

Esta é a read_text implementação real :

def read_text(self, encoding=None, errors=None):
    """
    Open the file in text mode, read it, and close the file.
    """
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
        return f.read()
Eyal Levin
fonte
2

Bem, se você precisar ler o arquivo linha por linha para trabalhar com cada linha, poderá usar

with open('Path/to/file', 'r') as f:
    s = f.readline()
    while s:
        # do whatever you want to
        s = f.readline()

Ou ainda melhor:

with open('Path/to/file') as f:
    for line in f:
        # do whatever you want to
Kirill
fonte
0

Em vez de recuperar o conteúdo do arquivo como uma única sequência, pode ser útil armazenar o conteúdo como uma lista de todas as linhas que o arquivo compreende :

with open('Path/to/file', 'r') as content_file:
    content_list = content_file.read().strip().split("\n")

Como pode ser visto, é necessário adicionar os métodos concatenados .strip().split("\n")à resposta principal neste tópico .

Aqui, .strip()apenas remove os caracteres de espaço em branco e nova linha nas terminações de toda a cadeia de arquivos e .split("\n")produz a lista real dividindo a cadeia de arquivos inteira em cada caractere de nova linha \ n .

Além disso, dessa forma, todo o conteúdo do arquivo pode ser armazenado em uma variável, o que pode ser desejado em alguns casos, em vez de fazer um loop sobre o arquivo linha por linha, como apontado nesta resposta anterior .

Andreas L.
fonte