50 votes

Python comment lire un nombre N de lignes à la fois

J'écris un code pour prendre un énorme fichier texte (plusieurs Go) N lignes à la fois, traiter ce lot, et passer aux N lignes suivantes jusqu'à ce que j'aie terminé le fichier entier. (Je ne me soucie pas que le dernier lot ne soit pas de la taille parfaite).

Je me suis renseigné sur l'utilisation d'itertools islice pour cette opération. Je pense que j'ai fait la moitié du chemin :

from itertools import islice
N = 16
infile = open("my_very_large_text_file", "r")
lines_gen = islice(infile, N)

for lines in lines_gen:
     ...process my lines...

Le problème est que je voudrais traiter le lot suivant de 16 lignes, mais il me manque quelque chose

0 votes

2 votes

@ken - L'OP demande comment faire cela en utilisant islice Dans ce message, l'OP demande comment faire cela avec yield .

0 votes

63voto

Sven Marnach Points 133943

islice() peut être utilisé pour obtenir le prochain n éléments d'un itérateur. Ainsi, list(islice(f, n)) retournera une liste des prochaines n lignes du fichier f . En utilisant ceci dans une boucle, vous obtiendrez le fichier par morceaux de n lignes. A la fin du fichier, la liste peut être plus courte, et finalement l'appel retournera une liste vide.

from itertools import islice
with open(...) as f:
    while True:
        next_n_lines = list(islice(f, n))
        if not next_n_lines:
            break
        # process next_n_lines

Une alternative est d'utiliser l'option motif de mérou :

with open(...) as f:
    for next_n_lines in izip_longest(*[f] * n):
        # process next_n_lines

1 votes

J'apprends python ces jours-ci, j'ai une question, idéalement si vous lisez une base de données ou un fichier d'enregistrements, vous devrez marquer les enregistrements comme lus (une autre colonne nécessaire) et dans le prochain lot vous commencerez à traiter les prochains enregistrements non marqués, comment cela est-il réalisé ici ? notamment ici next_n_lines = list(islice(infile, n))

0 votes

@zengr : Je ne comprends pas votre question. list(islice(infile, n)) obtiendra le prochain morceau de n lignes du fichier. Les fichiers savent ce que vous avez déjà lu, vous pouvez simplement continuer à lire.

0 votes

@Sven Dites, mon travail par lots est exécuté une fois par jour. J'ai un énorme fichier texte de 1M de lignes. Mais, je veux seulement lire les 1000 premières lignes le jour 1. Le travail s'arrête. Maintenant, jour 2 : je devrais commencer à traiter le même fichier à partir de la 1001ème ligne. Alors, comment maintenir cela, sauf en stockant le nombre de lignes à un autre endroit.

8voto

msw Points 25319

La question semble présumer qu'il est possible de gagner en efficacité en lisant un "énorme fichier texte" par blocs de N lignes à la fois. Cela ajoute une couche applicative de mise en mémoire tampon par rapport à l'application déjà hautement optimisée de stdio bibliothèque, ajoute de la complexité et ne vous apporte probablement rien.

Ainsi :

with open('my_very_large_text_file') as f:
    for line in f:
        process(line)

est probablement supérieur à toute autre solution en termes de temps, d'espace, de complexité et de lisibilité.

Voir aussi Les deux premières règles de Rob Pike , Les deux règles de Jackson y PEP-20 Le Zen de Python . Si tu voulais vraiment juste jouer avec islice tu aurais dû laisser de côté le truc des gros fichiers.

1 votes

Bonjour, La raison pour laquelle je dois traiter mon énorme fichier texte en blocs de N lignes est que je choisis une ligne au hasard dans chaque groupe de N. Ceci est pour une analyse bioinformatique, et je veux faire un fichier plus petit qui a une représentation égale de l'ensemble des données. En biologie, toutes les données ne sont pas égales ! Il existe peut-être un autre moyen (peut-être meilleur ?) de choisir un nombre X de lignes aléatoires réparties de manière égale dans un énorme ensemble de données, mais c'est la première chose à laquelle j'ai pensé. Merci pour les liens !

0 votes

@brokentypewriter c'est une question très différente pour laquelle il existe des échantillonnages beaucoup plus utiles statistiquement. Je vais chercher quelque chose dans le commerce et en faire une nouvelle question ici. Je mettrai un lien ici quand je l'aurai fait. L'auto-corrélation est un artefact triste à introduire.

0 votes

J'y ai répondu dans cette question à la place : stackoverflow.com/questions/6335839/

3voto

mouad Points 21520

Voici une autre façon d'utiliser groupe par :

from itertools import count, groupby

N = 16
with open('test') as f:
    for g, group in groupby(f, key=lambda _, c=count(): c.next()/N):
        print list(group)

Comment cela fonctionne :

Fondamentalement, groupby() regroupera les lignes en fonction de la valeur de retour du paramètre clé, et le paramètre clé est le nom de l'utilisateur. lambda fonction lambda _, c=count(): c.next()/N et en utilisant le fait que l'argument c sera lié à compter() lorsque le La fonction sera définie donc à chaque fois groupby() appellera la fonction lambda et évaluera la valeur de retour pour déterminer le mérou qui regroupera les lignes ainsi :

# 1 iteration.
c.next() => 0
0 / 16 => 0
# 2 iteration.
c.next() => 1
1 / 16 => 0
...
# Start of the second grouper.
c.next() => 16
16/16 => 1   
...

2voto

msw Points 25319

Puisque l'exigence a été ajoutée qu'il y ait une distribution statistiquement uniforme des lignes sélectionnées dans le fichier, je propose cette approche simple.

"""randsamp - extract a random subset of n lines from a large file"""

import random

def scan_linepos(path):
    """return a list of seek offsets of the beginning of each line"""
    linepos = []
    offset = 0
    with open(path) as inf:     
        # WARNING: CPython 2.7 file.tell() is not accurate on file.next()
        for line in inf:
            linepos.append(offset)
            offset += len(line)
    return linepos

def sample_lines(path, linepos, nsamp):
    """return nsamp lines from path where line offsets are in linepos"""
    offsets = random.sample(linepos, nsamp)
    offsets.sort()  # this may make file reads more efficient

    lines = []
    with open(path) as inf:
        for offset in offsets:
            inf.seek(offset)
            lines.append(inf.readline())
    return lines

dataset = 'big_data.txt'
nsamp = 5
linepos = scan_linepos(dataset) # the scan only need be done once

lines = sample_lines(dataset, linepos, nsamp)
print 'selecting %d lines from a file of %d' % (nsamp, len(linepos))
print ''.join(lines)

Je l'ai testé sur un fichier de données fictif de 3 millions de lignes comprenant 1,7 Go sur le disque. Le site scan_linepos a dominé le temps d'exécution qui prend environ 20 secondes sur mon bureau pas si chaud.

Juste pour vérifier les performances de sample_lines J'ai utilisé le timeit comme suit

import timeit
t = timeit.Timer('sample_lines(dataset, linepos, nsamp)', 
        'from __main__ import sample_lines, dataset, linepos, nsamp')
trials = 10 ** 4
elapsed = t.timeit(number=trials)
print u'%dk trials in %.2f seconds, %.2fµs per trial' % (trials/1000,
        elapsed, (elapsed/trials) * (10 ** 6))

Pour différentes valeurs de nsamp ; lorsque nsamp était de 100, un seul sample_lines achevé en 460µs et mis à l'échelle linéairement jusqu'à 10k échantillons à 47ms par appel.

La question suivante est naturellement Le hasard n'est pas du tout aléatoire ? et la réponse est "sous-cryptographique mais certainement très bien pour la bioinformatique".

0 votes

@brokentypewriter - merci pour cette agréable diversion de mon vrai travail o.O.

0 votes

@msw Solution géniale. Elle fonctionne très rapidement, et j'aime que random.sample prenne un échantillon sans remplacement. Le seul problème est que j'ai une erreur de mémoire lors de l'écriture de mes fichiers de sortie... mais je peux probablement le réparer moi-même. (La première chose que je vais essayer est d'écrire le fichier de sortie une ligne à la fois, au lieu de toutes les lignes jointes ensemble). Merci pour cette excellente solution ! J'ai 9 millions de lignes, je les échantillonne 11 fois dans une boucle, donc les mesures de gain de temps sont excellentes ! La manipulation des listes et le chargement de toutes les lignes dans les listes prenaient beaucoup trop de temps.

0 votes

@msw Je l'ai corrigé pour écrire chaque ligne dans le fichier de sortie une par une pour éviter les problèmes de mémoire. Tout fonctionne très bien ! Il faut 4 min 25 secondes pour l'exécuter, ce qui est bien mieux que 2+ heures pour exécuter la version précédente (itération sur des listes). J'aime beaucoup le fait que cette solution ne charge en mémoire que les lignes qui sont échantillonnées à partir de leur décalage. C'est une astuce soignée et efficace. Je peux dire que j'ai appris quelque chose de nouveau aujourd'hui !

1voto

utdemir Points 9107

Utilisation de la fonction chunker de Quelle est la manière la plus "pythique" d'itérer sur une liste par morceaux ? :

from itertools import izip_longest

def grouper(iterable, n, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(*args, fillvalue=fillvalue)

with open(filename) as f:
    for lines in grouper(f, chunk_size, ""): #for every chunk_sized chunk
        """process lines like 
        lines[0], lines[1] , ... , lines[chunk_size-1]"""

0 votes

@Sven Marnach ; Désolé, ce "grouper" doit être "chunker". Mais je pense(je ne comprends pas vraiment le vôtre) qu'il fait la même chose avec votre fonction grouper. edit : non il ne le fait pas.

0 votes

Toujours confus. 1. chunker() est défini avec deux paramètres et appelé avec trois. 2. Passage du site f comme seq essaiera de découper l'objet fichier, ce qui ne fonctionne tout simplement pas. Vous ne pouvez découper que des séquences.

0 votes

@Sven Marnach ; en fait, j'ai d'abord pris la première réponse de cette question sur ma réponse, j'ai créé le code pour cela, et j'ai pensé que la deuxième réponse est meilleure, et j'ai changé la fonction, mais j'ai oublié de changer l'appel de fonction. Et vous avez raison à propos du slicing, mon erreur, j'essaie de la corriger. merci.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X