Como posso ler grandes arquivos de texto em Python, linha por linha, sem carregá-lo na memória?

239

Eu preciso ler um arquivo grande, linha por linha. Digamos que o arquivo tenha mais de 5 GB e preciso ler cada linha, mas obviamente não quero usá- readlines()lo porque ele criará uma lista muito grande na memória.

Como o código abaixo funcionará para este caso? Ele xreadlinesestá lendo um por um na memória? A expressão do gerador é necessária?

f = (line for line in open("log.txt").xreadlines())  # how much is loaded in memory?

f.next()  

Além disso, o que posso fazer para ler isso em ordem inversa, assim como o tailcomando Linux ?

Eu encontrei:

http://code.google.com/p/pytailer/

e

" cabeça de python, cauda e verso lidas por linhas de um arquivo de texto "

Ambos funcionaram muito bem!

Bruno Rocha - rochacbruno
fonte
E o que posso fazer para ler isso de cauda? linha por linha, começando na última linha.
Bruno Rocha - rochacbruno
esta deve ser uma questão em separado
cmcginty

Respostas:

311

Forneci esta resposta porque o Keith, embora sucinto, não fecha o arquivo explicitamente

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
fonte
32
a pergunta ainda é: "para linha em infile" carregará meus 5 GB de linhas na memória? e, como posso ler da cauda?
Bruno Rocha - rochacbruno
68
@rochacbruno, lê apenas uma linha de cada vez. Quando a próxima linha for lida, a anterior será coletada como lixo, a menos que você tenha armazenado uma referência a ela em outro lugar
John La Rooy
1
@rochacbruno, Infelizmente, não é tão fácil ler as linhas na ordem inversa. Geralmente você gostaria de ler a partir do final do arquivo em pedaços de tamanho sensíveis (kilobytes para megabytes dizem) e dividida em caracteres de nova linha (ou qualquer que seja a linha que termina char é em sua plataforma)
John La Rooy
4
Obrigado! Encontrei a solução tail stackoverflow.com/questions/5896079/…
Bruno Rocha - rochacbruno
1
@bawejakunal, quer dizer se uma linha é muito longa para carregar na memória de uma só vez? Isso é incomum para um arquivo de texto . Em vez de usar o forloop que itera sobre as linhas, você pode usar chunk = infile.read(chunksize)para ler pedaços de tamanho limitado, independentemente do seu conteúdo. Você precisará procurar dentro das partes novas linhas.
John La Rooy
60

Tudo que você precisa fazer é usar o objeto de arquivo como um iterador.

for line in open("log.txt"):
    do_something_with(line)

Melhor ainda é usar o gerenciador de contexto nas versões recentes do Python.

with open("log.txt") as fileobject:
    for line in fileobject:
        do_something_with(line)

Isso fechará o arquivo automaticamente também.

Keith
fonte
2
Isso não está carregando o arquivo inteiro na memória?
Bruno Rocha - rochacbruno
17

Uma abordagem da velha escola:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
fonte
2
observação menor: para a segurança de exceção é recomendado o uso de 'com' declaração, no seu caso "com (filename, 'rt') aberto como fh:"
prokher
16
@ prokher: Sim, mas eu chamei isso de "velha escola".
PTBNL 16/01
15

É melhor usar um iterador. Relevante: http://docs.python.org/library/fileinput.html

Dos documentos:

import fileinput
for line in fileinput.input("filename"):
    process(line)

Isso evitará copiar o arquivo inteiro na memória de uma só vez.

Mikola
fonte
Embora os documentos mostrem o trecho como "uso típico", usá-lo não chama o close()método do FileInputobjeto de classe retornado quando o loop termina - portanto, eu evitaria usá-lo dessa maneira. No Python 3.2, eles finalmente se tornaram fileinputcompatíveis com o protocolo do gerenciador de contexto que trata desse problema (mas o código ainda não seria escrito da maneira mostrada).
martineau
7

Aqui está o que você faz se não tiver novas linhas no arquivo:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
fonte
Enquanto eu gosto desse método, você corre o risco de ter linhas em seu texto divididas em pedaços. Eu vi isso pessoalmente, o que significa que, se você estiver procurando por sstring no arquivo como eu, sentiria falta de alguns porque a linha em que estavam estava dividida em pedaços. Existe alguma forma de contornar isto? O uso de linhas de leitura não funcionou bem, pois eu recebi miscounts @Ariel Cabib
edo101
6

Por favor tente isto:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
fonte
Por favor explique?
Nikhil VJ 31/03
3
Dos documentos oficiais do Python: link O argumento de buffer opcional especifica o tamanho de buffer desejado do arquivo: 0 significa sem buffer, 1 significa buffer de linha, qualquer outro valor positivo significa usar um buffer de tamanho (aproximadamente) desse tamanho (em bytes). Um buffer negativo significa usar o padrão do sistema, que geralmente é buffer de linha para dispositivos tty e totalmente buffer para outros arquivos. Se omitido, o padrão do sistema é usado
das Jyoti
Salvei meu dia, no meu caso, com arquivos ~ ~ 4gb com dois manipuladores de arquivos (um lido e outro gravado) python estava travando e agora está tudo bem! Obrigado.
Xelt
@jyotidas Enquanto eu gosto desse método, você corre o risco de ter linhas em seu texto divididas em pedaços. Eu vi isso pessoalmente, o que significa que, se você estiver procurando por sstring no arquivo como eu, sentiria falta de alguns porque a linha em que estavam estava dividida em pedaços. Existe alguma forma de contornar isto? Usando readlines não funcionou bem como eu tenho miscounts
edo101
3

Eu não podia acreditar que poderia ser tão fácil quanto a resposta de @ john-la-rooy fazia parecer. Então, recriei o cpcomando usando leitura e gravação linha por linha. É LOUCO RÁPIDO.

#!/usr/bin/env python3.6

import sys

with open(sys.argv[2], 'w') as outfile:
    with open(sys.argv[1]) as infile:
        for line in infile:
            outfile.write(line)
Bruno Bronosky
fonte
NOTA: Como o python readlinepadroniza as terminações de linha, isso tem o efeito colateral de converter documentos com terminações de linha DOS em finais de \r\nlinha Unix de \n. Meu principal motivo para pesquisar sobre este tópico foi que eu precisava converter um arquivo de log que receba uma série de terminações de linha (porque o desenvolvedor usou cegamente várias bibliotecas .NET). Fiquei chocado ao descobrir que, depois do meu teste inicial de velocidade, não precisava voltar atrás e rstripas linhas. Já estava perfeito!
de Bruno Bronosky
2

O projeto blaze percorreu um longo caminho nos últimos 6 anos. Ele possui uma API simples que cobre um subconjunto útil de recursos do pandas.

O dask.dataframe cuida do chunking internamente, suporta muitas operações paralelizáveis ​​e permite exportar fatias de volta ao pandas facilmente para operações na memória.

import dask.dataframe as dd

df = dd.read_csv('filename.csv')
df.head(10)  # return first 10 rows
df.tail(10)  # return last 10 rows

# iterate rows
for idx, row in df.iterrows():
    ...

# group by my_field and return mean
df.groupby(df.my_field).value.mean().compute()

# slice by column
df[df.my_field=='XYZ'].compute()
jpp
fonte
2

Aqui está o código para carregar arquivos de texto de qualquer tamanho sem causar problemas de memória. Suporta arquivos de tamanho de gigabytes

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

faça o download do arquivo data_loading_utils.py e importe-o para o seu código

uso

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(data, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=self.process_lines)

O método process_lines é a função de retorno de chamada. Será chamado para todas as linhas, com os dados dos parâmetros representando uma única linha do arquivo por vez.

Você pode configurar a variável CHUNK_SIZE, dependendo das configurações de hardware da sua máquina.

Iyvin Jose
fonte
Enquanto eu gosto desse método, você corre o risco de ter linhas em seu texto divididas em pedaços. Eu vi isso pessoalmente, o que significa que, se você estiver procurando por sstring no arquivo como eu, sentiria falta de alguns porque a linha em que estavam estava dividida em pedaços. Existe alguma forma de contornar isto? Usando readlines não funcionou bem como eu tenho miscounts
edo101
0

Que tal agora? Divida seu arquivo em partes e, em seguida, leia-o linha por linha, porque quando você lê um arquivo, seu sistema operacional armazena em cache a próxima linha. Se você estiver lendo o arquivo linha por linha, não estará fazendo uso eficiente das informações em cache.

Em vez disso, divida o arquivo em partes, carregue toda a parte na memória e faça seu processamento.

def chunks(file,size=1024):
    while 1:

        startat=fh.tell()
        print startat #file's object current position from the start
        fh.seek(size,1) #offset from current postion -->1
        data=fh.readline()
        yield startat,fh.tell()-startat #doesnt store whole list in memory
        if not data:
            break
if os.path.isfile(fname):
    try:
        fh=open(fname,'rb') 
    except IOError as e: #file --> permission denied
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except Exception as e1: #handle other exceptions such as attribute errors
        print "Unexpected error: {0}".format(e1)
    for ele in chunks(fh):
        fh.seek(ele[0])#startat
        data=fh.read(ele[1])#endat
        print data
Arohi Gupta
fonte
Isso parece promissor. Isso é carregado por bytes ou por linhas? Eu tenho medo de que as linhas sejam quebradas se for por bytes .. como podemos carregar, digamos, 1000 linhas por vez e processar isso?
precisa
0

Obrigado! Recentemente, eu me converti em python 3 e fiquei frustrado usando o readlines (0) para ler arquivos grandes. Isso resolveu o problema. Mas para obter cada linha, eu tive que fazer algumas etapas extras. Cada linha foi precedida por um "b", que eu acho que estava no formato binário. O uso de "decodificação (utf-8)" mudou ascii.

Então eu tive que remover um "= \ n" no meio de cada linha.

Então eu divido as linhas na nova linha.

b_data=(fh.read(ele[1]))#endat This is one chunk of ascii data in binary format
        a_data=((binascii.b2a_qp(b_data)).decode('utf-8')) #Data chunk in 'split' ascii format
        data_chunk = (a_data.replace('=\n','').strip()) #Splitting characters removed
        data_list = data_chunk.split('\n')  #List containing lines in chunk
        #print(data_list,'\n')
        #time.sleep(1)
        for j in range(len(data_list)): #iterate through data_list to get each item 
            i += 1
            line_of_data = data_list[j]
            print(line_of_data)

Aqui está o código que começa logo acima de "imprimir dados" no código de Arohi.

John Haynes
fonte
0

Eu demonstrei uma abordagem de acesso aleatório no nível de bytes paralelos aqui nesta outra questão:

Obtendo o número de linhas em um arquivo de texto sem linhas de leitura

Algumas das respostas já fornecidas são boas e concisas. Eu gosto de alguns deles. Mas isso realmente depende do que você deseja fazer com os dados que estão no arquivo. No meu caso, eu só queria contar linhas o mais rápido possível em grandes arquivos de texto. Meu código pode ser modificado para fazer outras coisas também, como qualquer código.

Geoffrey Anderson
fonte
0

A melhor solução que encontrei sobre isso e tentei em arquivo de 330 MB.

lineno = 500
line_length = 8
with open('catfour.txt', 'r') as file:
    file.seek(lineno * (line_length + 2))
    print(file.readline(), end='')

Onde line_length é o número de caracteres em uma única linha. Por exemplo "abcd" tem o comprimento da linha 4.

Adicionei 2 no comprimento da linha para pular o caractere '\ n' e passar para o próximo caractere.

Ali Sajjad
fonte
-1

Isso pode ser útil quando você deseja trabalhar em paralelo e ler apenas blocos de dados, mas mantê-los limpos com novas linhas.

def readInChunks(fileObj, chunkSize=1024):
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        while data[-1:] != '\n':
            data+=fileObj.read(1)
        yield data
Adão
fonte
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

espero que isto ajude.

Sainik Kr Mahata
fonte
5
Isso não leria o arquivo inteiro na memória? A pergunta pergunta explicitamente como evitar isso, portanto, isso não responde à pergunta.
Paradoxo de Fermi