web-dev-qa-db-fra.com

Comment lire un fichier volumineux ligne par ligne

Je veux parcourir chaque ligne d'un fichier entier. Une façon de faire est de lire le fichier entier, de le sauvegarder dans une liste, puis de parcourir la ligne qui vous intéresse. Cette méthode utilise beaucoup de mémoire, je cherche donc une alternative.

Mon code jusqu'ici:

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)

L'exécution de ce code génère un message d'erreur: device active.

Aucune suggestion?

Le but est de calculer la similarité des chaînes par paires, ce qui signifie que pour chaque ligne du fichier, je souhaite calculer la distance de Levenshtein avec toutes les autres lignes.

507
384X21

La manière correcte, entièrement Pythonic, de lire un fichier est la suivante:

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

L'instruction with gère l'ouverture et la fermeture du fichier, y compris si une exception est levée dans le bloc interne. Le for line in f traite le fichier objet f comme un élément itérable, qui utilise automatiquement les E/S et la gestion de la mémoire en mémoire tampon de sorte que vous n'avez pas à vous soucier des fichiers volumineux.

Il devrait y avoir un - et de préférence un seul - moyen évident de le faire.

1228
Katriel

Deux moyens efficaces en termes de mémoire, classés par ordre décroissant (le premier est le meilleur) -

  1. utilisation de with - prise en charge à partir de python 2.5 et versions ultérieures
  2. utilisation de yield si vous voulez vraiment avoir le contrôle sur ce qu'il faut lire

1. utilisation de with

with est le moyen agréable et efficace utilisé par Pythonic pour lire de gros fichiers. avantages - 1) L'objet fichier est automatiquement fermé après la sortie du bloc d'exécution with. 2) gestion des exceptions à l'intérieur du bloc with. 3) La boucle memory for effectue une itération ligne par ligne dans le fichier f. en interne, il met en mémoire tampon IO (à optimiser sur des opérations coûteuses IO) et de la mémoire.

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

2. utilisation de yield

Parfois, on peut souhaiter un contrôle plus fin sur le nombre de lectures à chaque itération. Dans ce cas, utilisez iter & rendement . Notez qu'avec cette méthode, il faut explicitement fermer le fichier à la fin.

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()

Pièges et par souci d'exhaustivité - les méthodes ci-dessous ne sont pas aussi bonnes ou moins élégantes pour la lecture de fichiers volumineux, mais veuillez les lire pour mieux comprendre.

En Python, le moyen le plus courant de lire les lignes d'un fichier consiste à:

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

Lorsque cela est fait, cependant, la fonction readlines() (il en va de même pour la fonction read()) charge tout le fichier en mémoire, puis itère dessus. Une approche légèrement meilleure (les deux premières méthodes mentionnées sont les meilleures) pour les fichiers volumineux consiste à utiliser le module fileinput, comme suit:

import fileinput

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

l'appel fileinput.input() lit les lignes de manière séquentielle, mais ne les garde pas en mémoire après leur lecture ou même simplement ainsi, puisque file dans python est itérable.

Références

  1. Python avec instruction
122

Pour enlever les nouvelles lignes:

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

Avec support de nouvelle ligne universel , toutes les lignes de fichier texte semblent se terminer par '\n', quels que soient les terminateurs du fichier, '\r', '\n' ou '\r\n'.

EDIT - Pour spécifier la prise en charge de la nouvelle ligne universelle:

  • Python 2 sur Unix - open(file_path, mode='rU') - requis [merci @ Dave ]
  • Python 2 sous Windows - open(file_path, mode='rU') - facultatif
  • Python 3 - open(file_path, newline=None) - facultatif

Le paramètre newline est uniquement pris en charge dans Python 3 et par défaut à None. Le paramètre mode par défaut est 'r' dans tous les cas. La U est obsolète dans Python 3. Dans Python 2 sous Windows, un autre mécanisme semble traduire \r\n en \n.

Docs:

Pour préserver les terminateurs de ligne natifs:

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

Le mode binaire peut toujours analyser le fichier en lignes avec in. Chaque ligne aura ses terminaisons dans le fichier.

Merci à @ katrielalex , réponse , à Python open () doc, et iPython = expériences.

37
Bob Stein

c'est un moyen possible de lire un fichier en python:

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

il n'alloue pas une liste complète. Il parcourt les lignes.

17
Simon Bergot

Un peu de contexte pour savoir d’où je viens. Les extraits de code sont à la fin.

Lorsque je le peux, je préfère utiliser un outil open source tel que H2O pour effectuer des lectures de fichiers CSV parallèles aux performances extrêmement élevées, mais cet outil présente un ensemble de fonctionnalités limité. Je finis par écrire beaucoup de code pour créer des pipelines de données informatiques avant d'alimenter le cluster H2O pour l'apprentissage supervisé proprement dit.

Je lisais beaucoup plus rapidement des fichiers tels que le jeu de données HIGGS de 8 Go du dépôt UCI et même des fichiers CSV de 40 Go à des fins de science des données en ajoutant beaucoup de parallélisme à la fonction objet de pool et carte de la bibliothèque de multitraitement. Par exemple, la mise en cluster avec les recherches du voisin le plus proche ainsi que les algorithmes de cluster DBSCAN et de Markov nécessitent une certaine finesse de programmation parallèle pour contourner des problèmes de mémoire et de temps très difficiles.

En général, j'aime diviser le fichier en plusieurs parties à l'aide des outils gnu, puis glob-les les masquer pour les trouver et les lire en parallèle dans le programme python. J'utilise quelque chose comme 1000 + fichiers partiels couramment. Ces astuces contribuent énormément à la vitesse de traitement et aux limites de la mémoire.

Le pandas dataframe.read_csv est à thread unique. Vous pouvez donc réaliser ces astuces pour rendre pandas beaucoup plus rapide en exécutant un map () pour une exécution parallèle. Vous pouvez utiliser htop pour constater qu'avec dataframe.read_csv séquentiel ancien pandas, 100% du processeur sur un seul noyau constitue le goulet d'étranglement actuel dans pd.read_csv, pas le disque du tout.

J'ajouterais que j'utilise un disque SSD sur un bus de carte vidéo rapide, pas un disque HD en rotation sur un bus SATA6, plus 16 cœurs de processeur.

En outre, une autre technique que j'ai trouvée fonctionne très bien dans certaines applications est le fichier CSV parallèle qui lit tout dans un fichier géant, en commençant chaque ouvrier à un décalage différent dans le fichier, plutôt que de pré-diviser un gros fichier en plusieurs fichiers de pièce. Utilisez les fichiers search () et tell () de python dans chaque opérateur parallèle pour lire le gros fichier texte en bandes, à différents emplacements de début et de fin d'octets décalés d'octet dans le même fichier simultanément. Vous pouvez effectuer une recherche rationnelle sur les octets et renvoyer le nombre de sauts de ligne. C'est une somme partielle. Enfin, résumez les sommes partielles pour obtenir la somme globale lorsque la fonction de carte reviendra une fois les tâches terminées.

Voici quelques exemples de points de repère utilisant l'astuce de décalage d'octet parallèle:

J'utilise 2 fichiers: HIGGS.csv est de 8 Go. Il provient du référentiel d’apprentissage automatique UCI. all_bin .csv, 40,4 Go, provient de mon projet actuel. J'utilise 2 programmes: GNU programme wc fourni avec Linux et le programme pur python fastread.py que j'ai développé.

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

Cela correspond à une vitesse de traitement des fichiers d'environ 4,5 Go/s ou 45 Gb/s. Ce n’est pas un disque dur en rotation, mon ami. C’est en fait un Samsung Pro 950 SSD.

Vous trouverez ci-dessous le repère de vitesse pour le même fichier compté en ligne par gnu wc, un programme compilé en C pur.

Ce qui est cool, c’est que vous pouvez voir que mon programme pur python correspond essentiellement à la vitesse du programme C gnu wc compilé dans ce cas. Python est interprété mais C est compilé, donc c'est un exploit de vitesse assez intéressant, je pense que vous seriez d'accord. Bien sûr, il faut vraiment changer le programme wc en un programme parallèle pour éviter que les programmes de mon python ne s'en mêlent. Mais dans l’état actuel des choses, gnu wc n’est qu’un programme séquentiel. Vous faites ce que vous pouvez et python peut faire en parallèle aujourd'hui. La compilation en Cython pourrait peut-être m'aider (pour une autre fois). De plus, les fichiers mappés en mémoire n'ont pas encore été explorés.

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

Conclusion: la vitesse est bonne pour un programme pur python par rapport à un programme C. Cependant, il n’est pas suffisant d’utiliser le programme pur python par-dessus le programme C, au moins à des fins de décompte linéaire. Généralement, la technique peut être utilisée pour d'autres traitements de fichiers, donc ce code python est toujours bon.

Question: Est-ce que la compilation de la regex une seule fois et sa transmission à tous les travailleurs améliorera la vitesse? Réponse: La pré-compilation Regex n’aide en rien cette application. Je suppose que la raison en est que la surcharge de la sérialisation et de la création de processus pour tous les travailleurs est prépondérante.

Une dernière chose. La lecture de fichiers CSV en parallèle est-elle utile Le disque est-il le goulot d'étranglement ou s'agit-il de la CPU? Beaucoup de réponses dites top-notées sur stackoverflow contiennent la sagesse commune du développement selon laquelle il suffit d'un seul thread pour lire un fichier, mieux vous pouvez le faire, disent-ils. Sont-ils sûrs, cependant?

Découvrons-le:

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

Oh oui, oui c'est le cas. La lecture de fichiers en parallèle fonctionne assez bien. Eh bien voilà!

Ps. Au cas où certains d'entre vous voudraient savoir, que se passe-t-il si balanceFactor vaut 2 lors de l'utilisation d'un processus de travail unique? Eh bien, c’est horrible:

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

Parties clés du programme fastread.py python:

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

La définition de PartitionDataToWorkers est simplement un code séquentiel ordinaire. Je l'ai laissé au cas où quelqu'un d'autre voudrait s'exercer à la programmation parallèle. J'ai donné gratuitement les parties les plus difficiles: le code parallèle testé et fonctionnel, pour votre bénéfice d'apprentissage.

Merci à: Le projet open-source H2O d’Arno and Cliff et du personnel de H2O pour leurs logiciels de qualité et leurs vidéos d’instruction, qui m’ont inspiré pour ce lecteur de décalage d'octets parallèles haute performance python pur, comme indiqué ci-dessus. H2O lit les fichiers en parallèle en utilisant Java, est appelé par les programmes python et R, et est incroyablement rapide à la lecture de gros fichiers CSV.

10
Geoffrey Anderson

Katrielalex a fourni le moyen d'ouvrir et de lire un fichier.

Cependant, la façon dont votre algorithme se comporte lit tout le fichier pour chaque ligne du fichier. Cela signifie que la quantité totale de lecture d'un fichier - et le calcul de la distance de Levenshtein - sera effectuée N * N si N est la quantité de lignes dans le fichier. Puisque vous êtes préoccupé par la taille du fichier et que vous ne voulez pas le garder en mémoire, je suis préoccupé par le résultat durée d'exécution du second degré . Votre algorithme appartient à la classe d'algorithmes O (n ^ 2) qui peuvent souvent être améliorés avec la spécialisation.

Je suppose que vous connaissez déjà le compromis entre mémoire et durée d'exécution ici, mais vous voudrez peut-être vérifier s'il existe un moyen efficace de calculer plusieurs distances de Levenshtein en parallèle. Dans ce cas, il serait intéressant de partager votre solution ici.

Combien de lignes ont vos fichiers et sur quel type de machine (mem & cpu power) votre algorithme doit-il s'exécuter, et quelle est la durée d'exécution tolérée?

Le code ressemblerait à ceci:

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)

Mais les questions sont: comment stockez-vous les distances (matrice?) Et pouvez-vous tirer un avantage de la préparation, par exemple? outer_line pour le traitement ou la mise en cache de certains résultats intermédiaires pour la réutilisation.

5
cfi
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • Ouvrez votre fichier en lecture (r)
  • Lire le fichier entier et sauvegarder chaque ligne dans un liste (texte)
  • Parcourez la liste en imprimant chaque ligne.

Si vous souhaitez, par exemple, vérifier une ligne spécifique pour une longueur supérieure à 10, travaillez avec ce que vous avez déjà disponible.

for line in text:
    if len(line) > 10:
        print line
3
喬治扎菲

Je recommande fortement de ne pas utiliser le chargement de fichier par défaut car il est extrêmement lent. Vous devriez examiner les fonctions numpy et les fonctions IOpro (par exemple, numpy.loadtxt ()).

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

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

Ensuite, vous pouvez diviser votre opération par paires en morceaux:

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) 

Il est presque toujours beaucoup plus rapide de charger des données en morceaux et de faire ensuite des opérations matricielles dessus que de le faire élément par élément !!

2
John Haberstroh

De la documentation python pour fileinput . Input ():

Cela parcourt les lignes de tous les fichiers listés dans sys.argv[1:], par défaut à sys.stdin si la liste est vide.

de plus, la définition de la fonction est la suivante:

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

en lisant entre les lignes, cela me dit que files peut être une liste pour que vous puissiez avoir quelque chose comme:

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

Voir ici pour plus d'informations

2
KevinDTimm