16 de setembro de 2010

Garanta o fechamento dos arquivos

Há alguns dias rolou uma thread interessante na lista Python Brasil sobre leitura de arquivos texto.

O Leonardo Santagada fez uma observação importantíssima a respeito de como devemos abrir e ler arquivos em Python.


A recomendação dele é abrir e ler arquivos usando o seguinte pattern:
with open('arquivo.txt') as arquivo:
    for linha in arquivo:
        print linha.strip()

Em seguida, o Carlos Ribeiro explicou o motivo disso:
Só pra complementar, seria bom explicar a razão por trás disso tudo... acho
que muita gente não sabe o motivo.

A forma "simplista" de abrir & ler o arquivo com "for linha in
open('arquivo.txt')" sem fechar o arquivo explicitamente no final do loop
funciona bem no CPython e por isso muita gente assume que vai funcionar em
qualquer versão do Python. O que pouca gente sabe é que isso não é uma
feature da classe "file", mas sim um "efeito colateral" do sistema de
"garbage collection" ou "coletor de lixo".

Para piorar, o idioma simplificado foi amplamente divulgado durante vários
anos como sendo a forma "pitônica" de fazer leitura de arquivos. Por isso
muita gente ainda aprende este idioma como sendo o certo.

Explicando: no CPython, assim que a última referência para o objeto é
destruída (o que acontece "automaticamente" na saída do "for"), o objeto que
referencia o arquivo na memória é descartado *na hora* e como consequencia,
o arquivo em disco é fechado.

Em outras versões de Python o coletor de lixo funciona de forma bem
diferente e por isso, o arquivo pode ficar "pendurado", mantido aberto sem
necessidade, até que o lixo seja coletado (o que em alguns casos pode
ocorrer só no momento em que o script pára de rodar). O que o "with" faz é
garantir que o arquivo seja fechado no final do bloco, independente da
implementação do coletor de lixo. Por isso a sugestão do Leonardo é MUITO
importante.

--
Carlos Ribeiro
Consultoria em Projetos
twitter: http://twitter.com/carribeiro
blog: http://rascunhosrotos.blogspot.com
Valeu pela aula, pessoal.

[update em 29/jul/2013] a documentação oficial do Python recomenda o uso do with como sendo boa prática na seção Reading and Writing Files, do tutorial da linguagem.  O trecho que faz essa recomendação está no penúltimo parágrafo da seção.

Eu sou Vinicius Assef, um programador do século passado que gosta de Python, pratica Lean Development e acredita em Deus. Você pode me contactar por email ou twitter.

5 comentários:

  1. linhas=open('arquivo.txt').readlines()

    Esse pode usar tranquilo, certo?

    ResponderExcluir
  2. Não, Fernando.

    A forma que você sugeriu tem o mesmo problema relatado acima. Ou seja, o arquivo só será fechado pelo coletor de lixo. No Jython, por exemplo, esta tarefa é delegada ao coletor de lixo da JVM, que não atua de imediato como o da implementação em C do Python.

    Isso porque a especificação da linguagem diz apenas que ele deve ser coletado em algum momento depois que detectado que não há mais nenhuma referência ao objeto (nesse caso, o objeto "file" resultado de "open('arquivo.txt')") no escopo. Em algum momento, sem especificar quando, pode significar quando.

    Veja este artigo sobre o assunto.

    ResponderExcluir
  3. O problema do "with" para iniciantes é que ele esconde a parte mais importante: Tu tem que chamar o metodo close *SEMPRE* depois de abrir um arquivo, ou ele pode ficar aberto. O With faz isso sozinho, mas não é explicito sobre isso. O objeto file faz isso também quando é coletado pelo garbage collector, o que leva as pessoas a achar que depois que não tem mais referencia alguma ao arquivo ele será fechado.

    Ficou claro? O importante é o close, o fato do with fazer iss pra ti de forma elegante é bonito, mas é bom lembrar o que ele faz. O efeito colateral de quando se coleta a memória no python ele fechar os arquivos é algo que um bom programa não deve depender para funcionar.

    ResponderExcluir
  4. Hum, muito interessante... Então, eu sempre deixei meus arquivos abertos e não sabia =/

    Vivendo e aprendendo, grato pela explicação =)

    ResponderExcluir
  5. Vale lembrar que isso é quase sempre irrelevante quando trata-se de processos com um tempo de vida curto demais.

    De qualquer forma, o uso do with é mais idiomático, e deveria ser a abordagem de sempre :)

    ResponderExcluir

Marcadores