web-dev-qa-db-fra.com

Python comment lire N nombre 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 me fiche que le dernier lot ne soit pas de la taille parfaite).

J'ai lu comment utiliser isertools islice pour cette opération. Je pense que je suis à mi-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 prochain lot de 16 lignes, mais il me manque quelque chose

42
brokentypewriter

islice() peut être utilisé pour obtenir les prochains éléments n d'un itérateur. Ainsi, list(islice(f, n)) renverra une liste des prochaines lignes n du fichier f. L'utiliser dans une boucle vous donnera le fichier en morceaux de lignes n. À la fin du fichier, la liste peut être plus courte, et finalement l'appel renverra 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 le motif de groupeur :

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

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

Donc:

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

est probablement supérieur à toute alternative dans le temps, l'espace, la complexité et la lisibilité.

Voir aussi les deux premières règles de Rob Pike , les deux règles de Jackson , et PEP-20 Le Zen de Python . Si vous vouliez vraiment jouer avec islice, vous devriez avoir omis les gros fichiers.

6
msw

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 faux fichier de données de 3 millions de lignes comprenant 1,7 Go sur disque. Le scan_linepos a dominé l'exécution en prenant environ 20 secondes sur mon bureau pas si chaud.

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

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 diverses valeurs de nsamp; lorsque nsamp était égal à 100, un seul sample_lines terminé en 460 µs et mis à l'échelle linéairement jusqu'à 10 000 échantillons à 47 ms par appel.

La prochaine question naturelle est aléatoire est à peine aléatoire du tout? , et la réponse est "sous-cryptographique mais certainement très bien pour la bioinformatique".

2
msw

Voici une autre façon d'utiliser groupby :

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 ça marche:

Fondamentalement, groupby () regroupera les lignes par la valeur de retour du paramètre clé et le paramètre clé est la fonction lambdalambda _, c=count(): c.next()/N et en utilisant le fait que l'argument c sera lié à count () lorsque 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 groupera les lignes donc:

# 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   
...
1
mouad

En supposant que "batch" signifie vouloir traiter les 16 enregistrements en une seule fois plutôt qu'individuellement, lire le fichier un enregistrement à la fois et mettre à jour un compteur; lorsque le compteur atteint 16, traitez ce groupe.

interim_list = []
infile = open("my_very_large_text_file", "r")
ctr = 0
for rec in infile:
    interim_list.append(rec)
    ctr += 1
    if ctr > 15:
        process_list(interim_list)
        interim_list = []
        ctr = 0

the final group


liste_processus (liste_intermédiaire)
0
Joe

Fonction chunker utilisée depuis Quelle est la façon la plus “Pythonique” d'itérer une liste en 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
utdemir