Como ler um arquivo grande - linha por linha?

536

Eu quero iterar sobre cada linha de um arquivo inteiro. Uma maneira de fazer isso é lendo o arquivo inteiro, salvando-o em uma lista e passando pela linha de interesse. Este método usa muita memória, então estou procurando uma alternativa.

Meu código até agora:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

Executar este código dá uma mensagem de erro: device active.

Alguma sugestão?

O objetivo é calcular a similaridade de seqüências de pares, ou seja, para cada linha do arquivo, desejo calcular a distância de Levenshtein com todas as outras linhas.

384X21
fonte
4
Por que você precisa ler o arquivo inteiro novamente para cada linha? Talvez se você disser o que está tentando realizar, alguém possa sugerir uma abordagem melhor.
JJJ

Respostas:

1269

A maneira correta e totalmente Python de ler um arquivo é a seguinte:

with open(...) as f:
    for line in f:
        # Do something with 'line'

A withinstrução trata da abertura e do fechamento do arquivo, inclusive se uma exceção for gerada no bloco interno. Ele for line in ftrata o objeto de arquivo fcomo iterável, que usa automaticamente E / S em buffer e gerenciamento de memória para que você não precise se preocupar com arquivos grandes.

Deve haver uma - e preferencialmente apenas uma - maneira óbvia de fazê-lo.

Katriel
fonte
14
Sim, esta é a melhor versão com python 2.6 e acima
Simon Bergot
3
Pessoalmente, prefiro geradores e corotinas para lidar com pipelines de dados.
jldupont
4
qual seria a melhor estratégia se um arquivo é um arquivo de texto enorme, mas com uma linha e a idéia é processar palavras?
Mfcabrera
4
Alguém poderia explicar como for line in f:está funcionando? Quero dizer, como iterar sobre o objeto de arquivo é possível?
haccks
11
Se você iterar sobre um objeto, o Python procurará na lista de métodos de objetos um método especial chamado __iter__, que informa o que fazer. Os objetos de arquivo definem esse método especial para retornar um iterador sobre as linhas. (Grosso modo).
Katriel
130

Duas maneiras eficientes de memória na ordem de classificação (a primeira é a melhor) -

  1. uso de with- suportado no python 2.5 e superior
  2. uso de yieldse você realmente quer ter controle sobre o quanto ler

1. uso de with

withé a maneira pythonic agradável e eficiente de ler arquivos grandes. vantagens - 1) o objeto de arquivo é fechado automaticamente após sair do withbloco de execução. 2) manipulação de exceção dentro do withbloco. 3) o forloop de memória percorre o fobjeto de arquivo linha por linha. internamente, realiza E / S em buffer (para otimizar operações caras de E / S) e gerenciamento de memória.

with open("x.txt") as f:
    for line in f:
        do something with data

2. uso de yield

Às vezes, pode-se querer um controle mais refinado sobre o quanto ler em cada iteração. Nesse caso, use iter & yield . Observe que com esse método é necessário fechar explicitamente o arquivo no final.

def readInChunks(fileObj, chunkSize=2048):
    """
    Lazy function to read a file piece by piece.
    Default chunk size: 2kB.
    """
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        yield data

f = open('bigFile')
for chuck in readInChunks(f):
    do_something(chunk)
f.close()

Armadilhas e por uma questão de exaustividade - os métodos abaixo não são tão bons nem elegantes para ler arquivos grandes, mas leia para obter uma compreensão completa.

No Python, a maneira mais comum de ler linhas de um arquivo é fazer o seguinte:

for line in open('myfile','r').readlines():
    do_something(line)

Quando isso é feito, no entanto, a readlines()função (o mesmo se aplica à read()função) carrega o arquivo inteiro na memória e itera sobre ele. Uma abordagem um pouco melhor (os dois primeiros métodos mencionados são os melhores) para arquivos grandes é usar o fileinputmódulo, da seguinte maneira:

import fileinput

for line in fileinput.input(['myfile']):
    do_something(line)

a fileinput.input()chamada lê as linhas sequencialmente, mas não as mantém na memória depois de serem lidas ou simplesmente por isso, já que fileem python é iterável.

Referências

  1. Python com instrução
Srikar Appalaraju
fonte
9
-1 Nunca é uma boa ideia fazer isso for line in open(...).readlines(): <do stuff>. Por que você?! Você acabou de perder todos os benefícios do IO inteligente de buffer do Ithon, sem nenhum benefício.
Katriel #
5
@ Krikar: há um tempo e um lugar para dar todas as soluções possíveis para um problema; ensinar a um iniciante como fazer a entrada de arquivos não é. Ter a resposta correta enterrada no final de um longo post cheio de respostas erradas não é um bom ensinamento.
Katriel #
6
@Srikar: Você pode melhorar significativamente sua publicação colocando o caminho certo no topo, depois mencionando readlinese explicando por que não é uma coisa boa a se fazer (porque ele lê o arquivo na memória) e depois explicando o que o fileinputmódulo faz e por que você convém usá-lo sobre os outros métodos, explicando como o arquivo em partes melhora a IO e dando um exemplo da função de pedaços (mas mencionando que o Python já faz isso por você, para que você não precise). Mas apenas cinco maneiras de resolver um problema simples, quatro dos quais estão errados nesse caso, não é bom.
Katriel #
2
Tudo o que você adicionar por uma questão de integridade, adicione por último, não primeiro. Primeiro, mostre o caminho correto.
m000 20/09/2013
6
O @katrielalex revisitou minha resposta e constatou que merece reestruturação. Eu posso ver como a resposta anterior pode causar confusão. Esperamos que isso deixe claro para os usuários futuros.
Srikar Appalaraju
37

Para retirar novas linhas:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

Com o apoio de nova linha universal todas as linhas do arquivo de texto parece que vai ser terminada com '\n', quaisquer que sejam os terminadores no arquivo, '\r', '\n', ou '\r\n'.

EDIT - Para especificar o suporte universal de nova linha:

  • Python 2 no Unix - open(file_path, mode='rU')- obrigatório [obrigado @Dave ]
  • Python 2 no Windows - open(file_path, mode='rU')- opcional
  • Python 3 - open(file_path, newline=None)- opcional

O newlineparâmetro é suportado apenas no Python 3 e o padrão é None. O modeparâmetro é padronizado 'r'em todos os casos. A Uesta obsoleta em Python 3. Em Python 2 no Windows algum outro mecanismo parece traduzir \r\npara \n.

Documentos:

Para preservar terminadores de linha nativos:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

O modo binário ainda pode analisar o arquivo em linhas com in. Cada linha terá quaisquer terminadores existentes no arquivo.

Graças a @katrielalex 's resposta , do Python open () doc, e iPython experimentos.

Bob Stein
fonte
1
No Python 2.7, tive open(file_path, 'rU')que habilitar novas linhas universais.
Dave
17

Esta é uma maneira possível de ler um arquivo em python:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

não aloca uma lista completa. Ele itera sobre as linhas.

Simon Bergot
fonte
2
Enquanto isso funciona, definitivamente não é o caminho canônico. A maneira canônica é usar um wrapper de contexto, como with open(input_file) as f:. Isso poupa f.close()e garante que você não se esqueça de fechá-lo acidentalmente. Evita vazamentos de memória e tudo, muito importante ao ler arquivos.
Mast
1
Como o @Mast disse, esse não é o caminho canônico, portanto, voto negativo para isso.
Azuax 03/01
12

Algum contexto na frente de onde eu estou vindo. Os trechos de código estão no final.

Quando posso, prefiro usar uma ferramenta de código aberto como o H2O para fazer leituras paralelas de arquivos CSV de alto desempenho, mas essa ferramenta é limitada no conjunto de recursos. Acabo escrevendo muito código para criar pipelines de ciência de dados antes de alimentar o cluster de H2O para o aprendizado supervisionado adequado.

Tenho lido arquivos como o conjunto de dados HIGGS de 8 GB do repositório UCI e até arquivos CSV de 40 GB para fins de ciência de dados significativamente mais rapidamente, adicionando muito paralelismo ao objeto de pool da biblioteca de multiprocessamento e à função de mapa. Por exemplo, o agrupamento com pesquisas de vizinhos mais próximos e também os algoritmos de agrupamento DBSCAN e Markov requer alguma sutileza de programação paralela para contornar alguns problemas seriamente desafiadores da memória e do relógio de parede.

Eu geralmente gosto de dividir o arquivo em linhas em partes usando as ferramentas gnu primeiro e depois a glob-filemask todas para encontrar e ler em paralelo no programa python. Eu uso algo como 1000 arquivos parciais comumente. Fazer esses truques ajuda imensamente com a velocidade de processamento e os limites de memória.

O dataframe.read_csv do pandas é de thread único, para que você possa executar esses truques para tornar o panda muito mais rápido executando um map () para execução paralela. Você pode usar o htop para ver que, com os antigos pandas sequenciais dataframe.read_csv, 100% da CPU em apenas um núcleo é o gargalo real no pd.read_csv, e não o disco.

Devo acrescentar que estou usando um SSD no barramento rápido da placa de vídeo, não um HD giratório no barramento SATA6, além de 16 núcleos de CPU.

Além disso, outra técnica que eu descobri que funciona muito bem em alguns aplicativos é a leitura paralela de arquivos CSV em um arquivo gigante, iniciando cada trabalhador com um deslocamento diferente no arquivo, em vez de pré-dividir um arquivo grande em muitos arquivos de peça. Use o arquivo python seek () e tell () em cada trabalhador paralelo para ler o arquivo de texto grande em tiras, em diferentes locais de deslocamento de bytes e de início e de bytes no arquivo grande, tudo ao mesmo tempo simultaneamente. Você pode fazer uma localização regex nos bytes e retornar a contagem de feeds de linha. Esta é uma soma parcial. Finalmente, some as somas parciais para obter a soma global quando a função de mapa retornar após a conclusão dos trabalhadores.

A seguir, alguns exemplos de benchmarks usando o truque de deslocamento de bytes paralelos:

Eu uso 2 arquivos: HIGGS.csv é 8 GB. É do repositório de aprendizado de máquina da UCI. all_bin .csv tem 40,4 GB e é do meu projeto atual. Eu uso dois programas: o programa GNU wc, que acompanha o Linux, e o programa python fastread.py puro, que eu desenvolvi.

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

Isso significa uma velocidade de slurping de arquivo de 4,5 GB / s ou 45 Gb / s. Não há disco rígido girando, meu amigo. Na verdade, é um SSD Samsung Pro 950.

Abaixo está a referência de velocidade para o mesmo arquivo sendo contado em linha pelo gnu wc, um programa compilado em C puro.

O legal é que você pode ver que meu programa python puro correspondia essencialmente à velocidade do programa C compilado pelo gnu wc nesse caso. Python é interpretado, mas C é compilado, então esse é um feito bastante interessante de velocidade, acho que você concorda. Obviamente, o wc realmente precisa ser alterado para um programa paralelo e, em seguida, seria realmente melhor do que o meu programa python. Mas como está hoje, o gnu wc é apenas um programa seqüencial. Você faz o que pode e o python pode fazer paralelo hoje. A compilação do Cython pode me ajudar (por outro tempo). Os arquivos mapeados na memória também não foram explorados.

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

Conclusão: A velocidade é boa para um programa python puro comparado a um programa em C. No entanto, não é bom o suficiente usar o programa python puro sobre o programa C, pelo menos para fins de contagem de linha. Geralmente, a técnica pode ser usada para outro processamento de arquivo, portanto esse código python ainda é bom.

Pergunta: Compilar o regex apenas uma vez e passá-lo a todos os trabalhadores aumentará a velocidade? Resposta: A pré-compilação do Regex NÃO ajuda neste aplicativo. Suponho que a razão é que a sobrecarga da serialização e criação de processos para todos os trabalhadores esteja dominando.

Mais uma coisa. A leitura paralela de arquivos CSV ajuda mesmo? O disco é o gargalo ou a CPU? Muitas das chamadas respostas mais bem classificadas no stackoverflow contêm a sabedoria comum do desenvolvedor de que você só precisa de um thread para ler um arquivo, o melhor que pode fazer, dizem eles. Eles têm certeza?

Vamos descobrir:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

Ah sim, sim. A leitura paralela de arquivos funciona muito bem. Bem, lá vai você!

Ps. Caso alguns de vocês quisessem saber, e se o balanceFactor fosse 2 ao usar um único processo de trabalho? Bem, é horrível:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

Partes principais do programa python fastread.py:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

A definição para PartitionDataToWorkers é apenas um código seqüencial comum. Eu o deixei de fora caso alguém mais quisesse praticar como é a programação paralela. Dei de graça as partes mais difíceis: o código paralelo testado e funcionando, para seu benefício de aprendizado.

Obrigado a: O projeto H2O de código aberto, de Arno e Cliff e a equipe de H2O, por seus ótimos softwares e vídeos instrutivos, que me deram a inspiração para este leitor de offset de bytes paralelos de alto desempenho python puro, como mostrado acima. O H2O faz a leitura paralela de arquivos usando java, é passível de chamada pelos programas python e R e é louco, rápido, mais rápido do que qualquer coisa no planeta ao ler grandes arquivos CSV.

Geoffrey Anderson
fonte
Pedaços paralelos é basicamente o que é isso. Além disso, espero que o SSD e o Flash sejam os únicos dispositivos de armazenamento compatíveis com essa técnica. É improvável que o Spinning HD seja compatível.
Geoffrey Anderson
1
Como você considerou os arquivos de disco em cache do SO?
JamesThomasMoon1979
5

O Katrielalex forneceu o caminho para abrir e ler um arquivo.

No entanto, a maneira como o algoritmo funciona, lê o arquivo inteiro para cada linha do arquivo. Isso significa que a quantidade geral de leitura de um arquivo - e computando a distância de Levenshtein - será feita N * N se N for a quantidade de linhas no arquivo. Como você está preocupado com o tamanho do arquivo e não deseja mantê-lo na memória, estou preocupado com o tempo de execução quadrático resultante . Seu algoritmo está na classe O (n ^ 2) de algoritmos que geralmente podem ser aprimorados com especialização.

Suspeito que você já conheça a troca de memória versus tempo de execução aqui, mas talvez deseje investigar se existe uma maneira eficiente de calcular várias distâncias de Levenshtein em paralelo. Nesse caso, seria interessante compartilhar sua solução aqui.

Quantas linhas seus arquivos possuem e em que tipo de máquina (memória e poder da CPU) seu algoritmo deve ser executado, e qual é o tempo de execução tolerado?

O código seria semelhante a:

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

Mas as perguntas são: como você armazena as distâncias (matriz?) E pode obter uma vantagem de preparar, por exemplo, a linha_externa para processamento ou armazenar em cache alguns resultados intermediários para reutilização.

cfi
fonte
O que quero dizer é que este post não contém uma resposta para a pergunta, apenas mais algumas perguntas! OMI seria mais adequado como um comentário.
Katriel
1
@katriealex: Err. Estranho. Você viu os loops aninhados, expandindo sua própria resposta para atender à pergunta real? Posso remover minhas perguntas aqui da minha resposta e ainda há conteúdo suficiente para justificar o fornecimento como uma resposta - embora parcial -. Eu também aceitaria se você editasse sua própria resposta para incluir o exemplo de loop aninhado - que foi explicitamente solicitado pela pergunta - e então eu posso remover minha própria resposta com satisfação. Mas um voto negativo é algo que eu não entendo.
Cfi
Justo; Realmente não vejo a demonstração do loop aninhado como resposta à pergunta, mas acho que é bastante direcionada aos iniciantes. O voto negativo foi removido.
Katriel
3
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • Abra seu arquivo para leitura (r)
  • Leia o arquivo inteiro e salve cada linha em uma lista (texto)
  • Percorra a lista imprimindo cada linha.

Se você deseja, por exemplo, verificar uma linha específica para um comprimento maior que 10, trabalhe com o que você já tem disponível.

for line in text:
    if len(line) > 10:
        print line
loxsat
fonte
1
Não é o melhor para esta pergunta, mas esse código é útil principalmente no caso do que você está procurando.é "slurping" (lendo todo o arquivo de uma vez). Esse foi o meu caso e o google me trouxe aqui. +1. Além disso, para atomicidade, ou se você fizer demorado processamento no circuito pode endup mais rápido para ler o arquivo inteiro
NTG
1
Além disso, aprimorou um pouco o código: 1. não é necessário fechar depois com: ( docs.python.org/2/tutorial/inputoutput.html , procure por "É uma boa prática usar a palavra-chave with ...") 2 . texto pode ser processado após arquivo é lido (ouside de com loop ....)
NTG
2

Na documentação do python para fileinput .input ():

Isso itera sobre as linhas de todos os arquivos listados em sys.argv[1:], por padrão, sys.stdinse a lista estiver vazia

Além disso, a definição da função é:

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

lendo nas entrelinhas, isso me diz que filespode ser uma lista para que você possa ter algo como:

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

Veja aqui para mais informações

KevinDTimm
fonte
2

Eu recomendo fortemente não usar o carregamento de arquivo padrão, pois é terrivelmente lento. Você deve examinar as funções numpy e as funções IOpro (por exemplo, numpy.loadtxt ()).

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

Em seguida, você pode dividir sua operação em pares em pedaços:

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

É quase sempre muito mais rápido carregar dados em pedaços e depois executar operações de matriz nele do que fazê-lo elemento por elemento !!

John Haberstroh
fonte
0

Precisa ler frequentemente um arquivo grande da última posição?

Eu criei um script usado para cortar um arquivo access.log do Apache várias vezes ao dia. Então, eu precisava definir um cursor de posição na última linha analisada durante a última execução . Para este fim, eu usei file.seek()e file.seek()métodos que permite o armazenamento do cursor no arquivo.

Meu código:

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))
Samuel Dauzon
fonte
-2

A melhor maneira de ler arquivos grandes, linha por linha, é usar a função enumerar python

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line
Anurag Misra
fonte
3
Por que usar enumerar é melhor? O único benefício sobre a resposta aceita é que você obtém um índice do qual o OP não precisa e você está tornando o código menos legível.
precisa saber é