Pular para o conteúdo principal

Manipulando arquivos grandes em Python

Marcelo Toledo escreveu um artigo comparando a sua implmentação em C de um corretor ortográfico poposto por Peter Norgiv com a versão original em Python.

Porém Marcelo Toledo ao realizar essa comparação não levou em consideração que o exemplo desenvolvido por Peter Norvig era apensa um protótipo. Sendo assim, ele resolveu comparar ambos os programas, em C e Python, utilizando arquivos cada vez maiores e ilustrando a diferença de performance entre eles.

No código de Peter Norvig ele lê o arquivo de uma vez. Dá para imaginar o que acontenceu, baixa performance e "crash" com arquivos maiores de 100M devido a falta de RAM. :-(
Essa é a linha na qual o programa de Peter Norvig lê o arquivo e processa ele:
NWORDS = train(words(file('big.txt').read()))
Infelizmente Marcelo Toledo não procurou saber qual era o "bug" do código, deixando no ar uma idéia de que C é robusto é Python é uma linguagem não confiável.

Como eu fui questionado por um colega (Robson Peixoto) sobre o "porque" do problema com o código do corretor ortográfico. Resolvi então escrever um pequeno script que copia arquivos binários da forma correta. E assim fica claro que Python não tem problemas em manipular arquivos maiores que 100MB. ;-)

Segue um teste de uso do script copyfile.py

ruda@zion /tmp $ du -sh livecd-i686-installer-2007.0.iso
416M livecd-i686-installer-2007.0.iso

ruda@zion /tmp $ time ./copyfile.py livecd-i686-installer-2007.0
.iso teste.iso

real 0m39.708s
user 0m4.517s
sys 0m3.747s

Ou seja, sem usar muita CPU e nem mesmo RAM, esse script demorou 40
segundos para copiar um arquivo de 416MB, ou seja, 10MB/s aproximadamente.

Abaixo o código do script copyfile.py

#!/bin/env python
# -*- coding: utf-8 -*-
import sys

try:
origem = sys.argv[1]
destino = sys.argv[2]
except IndexError:
print "Modo de usar: copyfile.py origem destino"
sys.exit(1)

#Exemplo de leitura e gravação de arquivos grandes - usando modo binário
input = file(origem, 'rb')
output = file(destino, "wb")
for line in input:
output.write(line)

#Fechando os arquivos
input.close()
output.close()

Comentários

Rudá,

No caso de um arquivo binário (como o tal .iso), seria mais aconselhável usar file.read(tamanho) (http://docs.python.org/lib/bltin-file-objects.html#l2h-302), pois ler por linhas pode chegar ao extremo do arquivo inteiro ser considerado apenas uma (na falta de um \n).

Fora isso muito bom post, eu também odeio este tipo de difamação gratuita.
Gustavo,

Você tem razão.
Nesse caso ficaria assim:

data = input.read(4192)
while data:
output.write(data)
data = input.read(4192)

Os tempos de execução foram bem parecidos, mas poderia não ser. ;-)

real 0m41.049s
user 0m0.403s
sys 0m1.317s

A minha dúvida, e para isso teria que ver o código em C do objeto file, é se ele não é "esperto" na iteração com arquivos binários..?
Sidnei da Silva disse…
So para dar um ultimo toque, tem um jeito mais simples ainda de fazer isso, que eh usar a funcao 'copyfileobj' do modulo 'shutil':

input = open(sys.argv[0], 'rb')
output = open(sys.argv[1], 'wb')
shutil.copyfileobj(input, output)
Opa Sidnei,

Valeu pela dica. ;-)

De qualquer forma eu queria mostrar um exemplo de como ler o arquivo em blocos ou em linhas e não ele de uma vez como fez o Peter Norvig.

Foi ai que surgiu a idéia do script para copiar um arquivo grande, provando que Python, desde que programado de forma correta, consegue trabalhar muito bem com arquvos grandes.

Eu não quiz corrigir o código do corretor, pois achei que seria mais complicado de fazer e de testar.

Inclusive por que ele processa tudo de uma vez e joga na RAM. Na verdade seria necessário criar uma estrutura de armazenamento do arquivo processado, com índices de busca para que ese corretor funcione com dicionários maiores. :-(
Anônimo disse…
Quando eu precisei recuperar um backup de minhas fotos em umas mídias "bixadas" eu precisava percorrer arquivos binários (10 arquivos de 650MB) de um lado para outro (índice no final, pedaços de arquivo espalhado, etc...)

Usando file.read() e .seek() deixou o programa extremamente ineficaz. Solução: mmap (http://docs.python.org/lib/module-mmap.html).
Osvaldo,

Muito boa dica, eu nunca tinha ouvido falar no módulo "mmap". :-(

Nada como aprender coisas novas, ainda mais como os mestres. :-)
Anônimo disse…
Tem um artigo recente (4 dias atrás) do Fredrik Lundh falando de um problema semelhante: http://effbot.org/zone/wide-finder.htm

Postagens mais visitadas deste blog

18 de junho agora é #dornelesday

Hoje é um dia muito especial, pois hoje é dia de homenagear uma grande pessoa: Dorneles Treméa. Pois ele, com seus gestos simples, paciência, perseverança e generosidade, se tornou um exemplo para muitas pessoas, no mundo todo, dentro da comunidade de Software Livre e mais especificamente na comunidade Python e Plone. Apesar de ter nos deixado tão jovem, o tempo é relativo, por isso, tenho certeza que ele deixou uma marca profunda na vida de todos que conviveram com ele. Seja pela paz e alegria que ele transmitia, sempre de bom humor nas mais difíceis situações, seja pela disposição eu ajudar quem lhe pedia auxílio, seja pela dedicação que tinha pela sua família. Por isso, para mim e para muitas pessoas, o dia 18 de junho é a partir de hoje #dornelesday, que representa um dia para refletir sobre tudo de bom que nosso colega e amigo trouxe para nós com seu exemplo de vida. Que todo dia possamos nos inspirar com esse exemplo e possamos aprender, um pouco que seja, com este legado. Na ver

Social Network Research and Plone

I will have the next 6 months to develop a framework to help fast delivery of S ocial Network Services - SNS , it's the implementation task of my final work graduate research and has the title: Social Network Services: component based framework. And, because I have been using Plone in some projects to deliver Content Management Applications, like: company and community web sites, intranets, etc, in the last three years (ruda_porto IRC nick), I decide to use it as a base system to construct a SNS application framework. Of course that is not a simple task, since Social Networks Services Applications can be used for friendship, academic, professional or some kind of specialized networks, but the central point will be study the hole application domain (problem scope) and implement a framework (solution scope) to abstract social network core objects and features in a way it will be easy to extend and integrate with Plone content management core products and other third-part products fo

RelStorage: ZODB usando backend relacional (SGDBs)

Introdução O ZODB (banco de dados orientado à objetos para aplicalções Python ), originalmente criado como um componente do servidor de Aplicação Zope2 ( Z Object Publishing Environment ) , foi desenvolvido com o conceito de "Storage Layer", o qual abstrai o tipo de backend responsável pela persistência dos objetos. Historicamente, o primeiro Storage Layer desenvolvido para o ZODB foi FileStorage, que tem como objetivo ser simples e robusto, e por isso armazena todos os objetos e transações um único arquivo de forma sequencial. Para acessar os objetos através do seu caminho na hieraquia de objetos do ZODB (ex: obj2 = root['obj1']['obj2'] ), o FileStorage cria um arquivo auxiliar de índice e que deve estar completamente na memória para que o desempenho seja adequado. Compartilhando o ZODB Mais tarde, devido a necessidade de compartilhar um mesmo banco de dados por diversas instâncias de uma aplicação (ou diversas aplicações), foi desenvolvido o ZEO (Zope Ente