Question Comment lire un fichier volumineux, ligne par ligne dans Python


Je veux parcourir chaque ligne d'un fichier entier. Pour ce faire, il vous suffit de lire l'intégralité du fichier, de l'enregistrer dans une liste, puis de passer en revue la ligne d'intérêt. Cette méthode utilise beaucoup de mémoire, donc je suis à la recherche d'une alternative.

Mon code jusqu'à présent:

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 donne 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 dans le fichier, je veux calculer la distance de Levenshtein avec chaque autre ligne.


433
2017-11-04 13:26


origine


Réponses:


La façon correcte et entièrement Python de lire un fichier est la suivante:

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

le with instruction 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 l'objet fichier f en tant que itérable, qui utilise automatiquement la gestion des E / S et de la mémoire tampon afin que vous n'ayez pas à vous soucier de fichiers volumineux.

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


1097
2017-11-04 13:46



Deux moyens efficaces en mémoire dans l'ordre de classement (le premier est le meilleur) -

  1. utilisation de with - supporté depuis python 2.5 et supérieur
  2. utilisation de yield si vous voulez vraiment avoir le contrôle de la quantité à lire

1. utilisation de with

with est la manière pythonique agréable et efficace de lire des fichiers volumineux. avantages - 1) objet fichier est automatiquement fermé après la sortie de with bloc d'exécution. 2) traitement des exceptions à l'intérieur du with bloc. 3) mémoire for boucle itère à travers le f fichier objet ligne par ligne. En interne, il met en mémoire tampon les E / S (optimisées pour les opérations d'E / S coûteuses) et la gestion 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 vouloir un contrôle plus précis sur la quantité à lire dans chaque itération. Dans ce cas, utilisez iter & rendement. Notez qu'avec cette méthode, vous devez fermer explicitement 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()

Les pièges et par souci d'exhaustivité - Les méthodes ci-dessous ne sont pas aussi bonnes ou pas aussi élégantes pour lire des fichiers volumineux, mais veuillez lire pour obtenir une compréhension arrondie.

En Python, la méthode la plus courante pour lire les lignes d'un fichier consiste à:

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

Quand cela est fait, cependant, le readlines() fonction (même chose pour read() function) charge le fichier entier 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 gros fichiers consiste à utiliser le fileinput module, comme suit:

import fileinput

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

la fileinput.input() call lit des lignes de façon séquentielle, mais ne les garde pas en mémoire après leur lecture ou même simplement file en python est itérable.

Les références

  1. Python avec déclaration

92
2017-11-04 13:31



Pour supprimer les nouvelles lignes:

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

Avec support universel newline toutes les lignes de fichiers texte semblent se terminer par '\n', quels que soient les terminateurs dans le fichier, '\r', '\n', ou '\r\n'.

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

  • Python 2 sur Unix - open(file_path, mode='rU') - Champs obligatoires [Merci @Dave]
  • Python 2 sur Windows - open(file_path, mode='rU') - optionnel
  • Python 3 - open(file_path, newline=None) - optionnel

le newline paramètre est uniquement pris en charge dans Python 3 et par défaut None. le mode paramètre par défaut à 'r' dans tous les cas. le U est déconseillé dans Python 3. Dans Python 2 sous Windows, d'autres mécanismes semblent traduire \r\n à \n.

Docs:

Pour préserver les terminaisons de ligne natives:

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 tous les terminateurs qu'il a dans le fichier.

Grâce à @katrielalexde répondre, Python ouvrir() doc, et iPython expériences.


30
2017-09-15 15:07



c'est une façon 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 itère sur les lignes.


15
2017-11-04 13:33



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

Quand je peux, je préfère utiliser un outil open source comme H2O pour faire des lectures de fichiers CSV parallèles à très hautes performances, mais cet outil est limité dans les fonctionnalités. Je finis par écrire beaucoup de code pour créer des pipelines de science des données avant d'alimenter en cluster H2O pour l'apprentissage supervisé proprement dit.

J'ai lu des fichiers comme 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 data science beaucoup plus rapidement en ajoutant beaucoup de parallélisme à l'objet pool et à la fonction map du multiprocessing. Par exemple, la mise en grappes avec les recherches de plus proches voisins et les algorithmes de clustering DBSCAN et Markov nécessitent une certaine finesse de programmation parallèle pour contourner certains problèmes de mémoire et de temps d'horloge.

J'ai l'habitude de décomposer le fichier en rangs en utilisant d'abord des outils gnu puis de les glob-filemask pour les trouver et les lire en parallèle dans le programme python. J'utilise quelque chose comme 1000+ fichiers partiels couramment. Faire ces trucs aide énormément la vitesse de traitement et les limites de la mémoire.

Le pandas dataframe.read_csv est à un seul thread, donc vous pouvez faire ces trucs pour rendre les pandas plus rapides en exécutant une map () pour l'exécution en parallèle. Vous pouvez utiliser htop pour voir que, avec les vieux pandas séquentiels, dataframe.read_csv, 100% cpu sur un seul cœur est le goulot d'étranglement réel de pd.read_csv, pas du tout le disque.

Je devrais ajouter que j'utilise un SSD sur le bus de carte vidéo rapide, pas un HD tournant sur le bus SATA6, plus 16 cœurs de processeur.

En outre, une autre technique que j'ai découverte fonctionne très bien dans certaines applications: la lecture parallèle de fichiers CSV dans un seul fichier géant, chaque opérateur démarrant à un décalage différent dans le fichier, plutôt que de diviser un gros fichier en plusieurs fichiers. Utilisez le fichier python seek () et tell () dans chaque agent parallèle pour lire le gros fichier texte en bandes, à différents octets de début et de fin d'octet et de décalage d'octets dans le gros fichier, tous simultanément. Vous pouvez faire un findall regex sur les octets, et renvoyer le nombre de sauts de ligne. Ceci est une somme partielle. Enfin, additionnez les sommes partielles pour obtenir la somme globale lorsque la fonction de la carte revient après la fin des travaux.

Vous trouverez ci-dessous quelques exemples de tests utilisant l’outil de décalage parallèle:

J'utilise 2 fichiers: HIGGS.csv est de 8 GB. Il provient du référentiel d'apprentissage machine UCI. all_bin .csv est de 40,4 Go et provient de mon projet actuel. J'utilise 2 programmes: le programme GNU 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

C'est environ 4,5 Go / s, ou 45 Go / s, la vitesse de slurping de fichiers. Ce n'est pas un disque dur qui tourne, mon ami. C'est en fait un Samsung Pro 950 SSD.

Ci-dessous, le repère de vitesse pour le même fichier est 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 python pur correspondait essentiellement à la vitesse du programme C compilé par gnu wc dans ce cas. Python est interprété mais C est compilé, donc c'est un tour de vitesse assez intéressant, je pense que vous serez d'accord. Bien sûr, nous avons vraiment besoin d'être changé en un programme parallèle, et alors il serait vraiment battre les chaussettes de mon programme python. Mais dans sa forme actuelle, gnu wc n'est qu'un programme séquentiel. Vous faites ce que vous pouvez, et Python peut faire parallèlement aujourd'hui. La compilation Cython pourrait peut-être m'aider (pour un autre moment). 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 python pur par rapport à un programme en C. Cependant, il ne suffit pas d'utiliser le programme python pur sur le programme C, au moins pour le comptage en ligne. Généralement, la technique peut être utilisée pour d'autres traitements de fichiers, donc ce code python est toujours correct.

Question: Compiler le regex une seule fois et le transmettre à tous les travailleurs va-t-il améliorer la vitesse? Réponse: La pré-compilation Regex n'aide pas dans 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.

Encore une chose. Est-ce que la lecture de fichiers CSV parallèle peut même aider? Le disque est-il le goulot d'étranglement, ou est-ce le processeur? Beaucoup de soi-disant réponses les mieux cotées sur stackoverflow contiennent la sagesse commune de développement que vous avez seulement besoin d'un fil pour lire un fichier, mieux que vous pouvez 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 vrai. La lecture de fichiers parallèles fonctionne assez bien. Eh bien voilà!

Ps. Dans le cas où certains d'entre vous voulaient savoir, que se passerait-il si le facteur de balance était de 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 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

Le def pour PartitionDataToWorkers est juste un code séquentiel ordinaire. Je l'ai laissé de côté au cas où quelqu'un d'autre voudrait s'entraîner à 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 H2O open-source, par Arno et Cliff et le personnel de H2O pour leurs superbes logiciels et vidéos pédagogiques, qui m'ont inspiré pour ce lecteur offset à octets parallèles en python haute performance tel que montré ci-dessus. H2O fait la lecture de fichiers en parallèle en utilisant java, est appelable par les programmes python et R, et est fou rapide, plus rapide que n'importe quoi sur la planète à lire de gros fichiers CSV.


5
2018-02-02 16:48



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

Cependant, la façon dont votre algorithme va, il lit le fichier entier pour chaque ligne du fichier. Cela signifie la quantité totale de lire un fichier - et le calcul de la Levenshtein distance - sera fait 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 exécution quadratique. Votre algorithme est dans la classe d'algorithmes O (n ^ 2) qui peut souvent être améliorée avec spécialisation.

Je soupçonne que vous connaissez déjà le compromis entre mémoire et temps d'exécution, mais peut-être voudriez-vous rechercher s'il existe un moyen efficace de calculer plusieurs distances Levenshtein en parallèle. Si c'est le cas, il serait intéressant de partager votre solution ici.

Combien de lignes ont vos fichiers et sur quel type de machine (puissance mem & cpu) votre algorithme doit-il fonctionner et quel est le temps d'exécution toléré?

Le code ressemblerait à:

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 de savoir comment vous stockez les distances (matrice?) Et pouvez-vous obtenir un avantage de préparation par ex. the outer_line pour le traitement ou la mise en cache de certains résultats intermédiaires pour la réutilisation.


4
2017-11-04 14:09



#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 pour le lire (r)
  • Lisez tout le fichier et enregistrez chaque ligne dans un liste (texte)
  • Bouclez la liste en imprimant chaque ligne.

Si vous voulez, 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
2017-07-30 02:01



De la documentation python pour entrée de fichier.contribution():

Cette itération sur les lignes de tous les fichiers répertorié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 afin 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
2017-11-04 13:32



Je recommande fortement de ne pas utiliser le chargement de fichier par défaut car il est terriblement lent. Vous devriez regarder dans 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 paire 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 des opérations matricielles dessus que de le faire élément par élément !!


2
2017-10-17 19:39



La meilleure façon de lire un gros fichier, ligne par ligne est d'utiliser python énumérer fonction

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

-3
2017-08-24 07:02