Comment lire des fichiers texte volumineux en Python, ligne par ligne, sans les charger en mémoire?

239

J'ai besoin de lire un gros fichier, ligne par ligne. Disons que le fichier a plus de 5 Go et que je dois lire chaque ligne, mais je ne veux évidemment pas l'utiliser readlines()car cela créera une très grande liste en mémoire.

Comment le code ci-dessous fonctionnera-t-il dans ce cas? Se xreadlineslit-il un par un en mémoire? L'expression du générateur est-elle nécessaire?

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

f.next()  

De plus, que puis-je faire pour lire ceci dans l'ordre inverse, tout comme la tailcommande Linux ?

J'ai trouvé:

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

et

" tête, queue et arrière en python lus par les lignes d'un fichier texte "

Les deux ont très bien fonctionné!

Bruno Rocha - rochacbruno
la source
Et que puis-je faire pour lire ceci de la queue? ligne par ligne, en commençant par la dernière ligne.
Bruno Rocha - rochacbruno
cela devrait être une question distincte
cmcginty

Réponses:

311

J'ai fourni cette réponse parce que Keith, bien que succinct, ne ferme pas le fichier explicitement

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)
John La Rooy
la source
32
la question est toujours, "pour la ligne en infile" chargera mes 5 Go de lignes dans la mémoire? et, comment lire à partir de la queue?
Bruno Rocha - rochacbruno
68
@rochacbruno, il ne lit qu'une ligne à la fois. Lorsque la ligne suivante est lue, la précédente sera récupérée à moins que vous n'ayez stocké une référence ailleurs
John La Rooy
1
@rochacbruno, La lecture des lignes dans l'ordre inverse n'est malheureusement pas aussi facile à faire efficacement. En règle générale, vous souhaitez lire la fin du fichier en morceaux de taille raisonnable (kilo-octets à mégaoctets, par exemple) et diviser en caractères de nouvelle ligne (ou quel que soit le caractère de fin de ligne sur votre plate-forme)
John La Rooy
4
Merci! J'ai trouvé la solution de queue stackoverflow.com/questions/5896079/…
Bruno Rocha - rochacbruno
1
@bawejakunal, Voulez-vous dire si une ligne est trop longue pour être chargée en mémoire à la fois? C'est inhabituel pour un fichier texte . Au lieu d'utiliser une forboucle qui itère sur les lignes, vous pouvez utiliser chunk = infile.read(chunksize)pour lire des morceaux de taille limitée quel que soit leur contenu. Vous devrez chercher vous-même dans les morceaux pour les nouvelles lignes.
John La Rooy
60

Il vous suffit d'utiliser l'objet fichier comme itérateur.

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

Encore mieux utilise le gestionnaire de contexte dans les versions récentes de Python.

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

Cela fermera automatiquement le fichier également.

Keith
la source
2
Cela ne charge pas le fichier entier dans la mémoire?
Bruno Rocha - rochacbruno
17

Une approche old school:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()
PTBNL
la source
2
remarque mineure: pour des raisons de sécurité, il est recommandé d'utiliser l'instruction 'with', dans votre cas "avec open (nom de fichier, 'rt') comme fh:"
prokher
16
@prokher: Oui, mais j'ai appelé ça "la vieille école".
PTBNL
15

Il vaut mieux utiliser un itérateur à la place. Pertinent: http://docs.python.org/library/fileinput.html

De la documentation:

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

Cela évitera de copier le fichier entier en mémoire à la fois.

Mikola
la source
Bien que les documents montrent l'extrait de code comme une «utilisation standard», son utilisation n'appelle pas la close()méthode de l' FileInputobjet classe retourné lorsque la boucle se termine - j'éviterais donc de l'utiliser de cette façon. En Python 3.2, ils ont finalement rendu fileinputcompatible avec le protocole du gestionnaire de contexte qui résout ce problème (mais le code ne serait toujours pas écrit de la manière indiquée).
martineau
7

Voici ce que vous faites si vous n'avez pas de retour à la ligne dans le fichier:

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)
Ariel Cabib
la source
Bien que j'aime cette méthode, vous courez le risque d'avoir une ligne dans votre texte divisée en morceaux. Je l'ai vu personnellement, ce qui signifie que si vous recherchez des chaînes dans le fichier comme je l'étais, j'en manquerais parce que la ligne à laquelle elles se trouvaient était coupée en morceaux. Y a-t-il un moyen de contourner ceci? L'utilisation de readlines n'a pas bien fonctionné car j'ai eu des erreurs de compte @Ariel Cabib
edo101
6

Veuillez essayer ceci:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line
jyoti das
la source
S'il vous plaît, expliquez?
Nikhil VJ
3
À partir des docmunets officiels de Python: lien L'argument de mise en mémoire tampon facultatif spécifie la taille de mémoire tampon souhaitée du fichier: 0 signifie non tamponné, 1 signifie ligne tamponné, toute autre valeur positive signifie utiliser un tampon de (approximativement) cette taille (en octets). Une mise en mémoire tampon négative signifie utiliser la valeur par défaut du système, qui est généralement mise en mémoire tampon de ligne pour les périphériques tty et entièrement mise en mémoire tampon pour les autres fichiers. S'il est omis, le système par défaut est utilisé
jyoti das
J'ai sauvé ma journée, dans mon cas, avec> ~ 4 Go de fichiers avec deux gestionnaires de fichiers (l'un en lecture, l'autre en écriture), python était suspendu et maintenant ça va! Merci.
Xelt
@jyotidas Bien que j'aime cette méthode, vous courez le risque de voir la ligne de votre texte divisée en morceaux. Je l'ai vu personnellement, ce qui signifie que si vous recherchez des chaînes dans le fichier comme je l'étais, j'en manquerais parce que la ligne à laquelle elles se trouvaient était coupée en morceaux. Y a-t-il un moyen de contourner ceci? L'utilisation des lignes de
lecture
3

Je ne pouvais pas croire que cela pourrait être aussi simple que la réponse de @ john-la-rooy le faisait croire. J'ai donc recréé la cpcommande en utilisant la lecture et l'écriture ligne par ligne. C'est FOU RAPIDE.

#!/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
la source
REMARQUE: Parce que python readlinestandardise les fins de ligne, cela a pour effet secondaire de convertir des documents avec des fins de ligne DOS de en fins de \r\nligne Unix de \n. Toute ma raison pour rechercher ce sujet était que je devais convertir un fichier journal qui reçoit un mélange de fins de ligne (parce que le développeur a utilisé aveuglément diverses bibliothèques .NET). J'ai été choqué de constater qu'après mon test de vitesse initial, je n'avais pas besoin de revenir en arrière et rstriples lignes. C'était déjà parfait!
Bruno Bronosky
2

Le projet Blaze a parcouru un long chemin au cours des 6 dernières années. Il dispose d'une API simple couvrant un sous-ensemble utile de fonctionnalités de pandas.

dask.dataframe prend en charge la segmentation en interne, prend en charge de nombreuses opérations parallélisables et vous permet d'exporter facilement des tranches vers des pandas pour des opérations en mémoire.

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
la source
2

Voici le code pour charger des fichiers texte de n'importe quelle taille sans causer de problèmes de mémoire. Il prend en charge des fichiers de taille gigaoctet

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

téléchargez le fichier data_loading_utils.py et importez-le dans votre code

usage

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)

La méthode process_lines est la fonction de rappel. Il sera appelé pour toutes les lignes, avec des données de paramètres représentant une seule ligne du fichier à la fois.

Vous pouvez configurer la variable CHUNK_SIZE en fonction des configurations matérielles de votre machine.

Iyvin Jose
la source
Bien que j'aime cette méthode, vous courez le risque d'avoir une ligne dans votre texte divisée en morceaux. Je l'ai vu personnellement, ce qui signifie que si vous recherchez des chaînes dans le fichier comme je l'étais, j'en manquerais parce que la ligne à laquelle elles se trouvaient était coupée en morceaux. Y a-t-il un moyen de contourner ceci? L'utilisation de readlines n'a pas bien fonctionné car j'ai eu des erreurs de calcul
edo101
0

Que dis-tu de ça? Divisez votre fichier en morceaux, puis lisez-le ligne par ligne, car lorsque vous lisez un fichier, votre système d'exploitation met en cache la ligne suivante. Si vous lisez le fichier ligne par ligne, vous n'utilisez pas efficacement les informations mises en cache.

Au lieu de cela, divisez le fichier en morceaux et chargez le morceau entier en mémoire, puis effectuez votre traitement.

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
la source
Cela semble prometteur. S'agit-il d'un chargement par octets ou par lignes? J'ai peur que les lignes soient coupées si c'est par octets .. comment pouvons-nous charger disons 1000 lignes à la fois et traiter cela?
Nikhil VJ
0

Je vous remercie! J'ai récemment converti en python 3 et j'ai été frustré en utilisant readlines (0) pour lire des fichiers volumineux. Cela a résolu le problème. Mais pour obtenir chaque ligne, j'ai dû faire quelques étapes supplémentaires. Chaque ligne était précédée d'un "b" qui, je suppose, était au format binaire. L'utilisation de "decode (utf-8)" l'a changé ascii.

Ensuite, j'ai dû supprimer un "= \ n" au milieu de chaque ligne.

Ensuite, j'ai divisé les lignes sur la nouvelle ligne.

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)

Voici le code commençant juste au-dessus des "données d'impression" dans le code d'Arohi.

John Haynes
la source
0

J'ai démontré une approche d'accès aléatoire au niveau octet parallèle ici dans cette autre question:

Obtention du nombre de lignes dans un fichier texte sans lignes de lecture

Certaines des réponses déjà fournies sont agréables et concises. J'aime certains d'entre eux. Mais cela dépend vraiment de ce que vous voulez faire avec les données contenues dans le fichier. Dans mon cas, je voulais juste compter les lignes, aussi vite que possible sur les gros fichiers texte. Mon code peut être modifié pour faire d'autres choses aussi bien sûr, comme n'importe quel code.

Geoffrey Anderson
la source
0

La meilleure solution que j'ai trouvée à ce sujet, et je l'ai essayée sur un fichier de 330 Mo.

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

Où line_length est le nombre de caractères sur une seule ligne. Par exemple, "abcd" a une longueur de ligne 4.

J'ai ajouté 2 longueurs de ligne pour ignorer le caractère «\ n» et passer au caractère suivant.

Ali Sajjad
la source
-1

Cela peut être utile lorsque vous souhaitez travailler en parallèle et lire uniquement des morceaux de données, mais les garder propres avec de nouvelles lignes.

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
Adam
la source
-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

J'espère que cela t'aides.

Sainik Kr Mahata
la source
5
Cela ne lirait-il pas l'intégralité du fichier en mémoire? La question demande explicitement comment éviter cela, donc cela ne répond pas à la question.
Paradoxe de Fermi