web-dev-qa-db-fra.com

Comment sauter à une ligne particulière dans un fichier texte énorme?

Existe-t-il des alternatives au code ci-dessous:

startFromLine = 141978 # or whatever line I need to jump to

urlsfile = open(filename, "rb", 0)

linesCounter = 1

for line in urlsfile:
    if linesCounter > startFromLine:
        DoSomethingWithThisLine(line)

    linesCounter += 1

Si je traite un fichier texte volumineux (~15MB) avec des lignes de longueur inconnue, mais différente, et que je dois passer à une ligne particulière dont je connais le numéro par avance Je me sens mal en les traitant un par un quand je sais que je pourrais ignorer au moins la première moitié du fichier. Vous recherchez une solution plus élégante s'il y en a.

96
user63503

linecache :

Le module linecache permet d’obtenir n'importe quelle ligne d’un fichier source Python, tout en essayant d’optimiser en interne, à l’aide d’un cache, cas typique où plusieurs lignes sont lues à partir d’un seul fichier. Ceci est utilisé par le module traceback pour récupérer les lignes source à inclure dans la trace formatée ...

27
John Ellinwood

Vous ne pouvez pas avancer sans lire le fichier au moins une fois, car vous ne savez pas où se trouvent les sauts de ligne. Vous pourriez faire quelque chose comme:

# Read in the file once and build a list of line offsets
line_offset = []
offset = 0
for line in file:
    line_offset.append(offset)
    offset += len(line)
file.seek(0)

# Now, to skip to line n (with the first line being line 0), just do
file.seek(line_offset[n])
97
Adam Rosenfield

Vous n'avez pas vraiment beaucoup d'options si les lignes sont de longueurs différentes ... vous devez malheureusement traiter les caractères de fin de ligne pour savoir quand vous avez progressé vers la ligne suivante.

Vous pouvez toutefois accélérer considérablement cette opération ET réduire l'utilisation de la mémoire en modifiant le dernier paramètre "ouvert" sur un paramètre différent de 0.

0 signifie que la lecture du fichier n'a pas de mémoire tampon, ce qui est très lent et nécessite beaucoup de disque. 1 signifie que le fichier est mis en mémoire tampon, ce qui constituerait une amélioration. Tout ce qui dépasse 1 (disons 8k .. c'est-à-dire 8096 ou plus) lit des morceaux du fichier en mémoire. Vous y accédez toujours via for line in open(etc):, mais python ne va que petit à petit, en supprimant chaque morceau mis en mémoire tampon après son traitement.

19
Jarret Hardie

Je suis probablement gâté par l'abondance de bélier, mais 15 M n'est pas énorme. Lire dans la mémoire avec readlines() est ce que je fais habituellement avec des fichiers de cette taille. Accéder à une ligne après cela est trivial.

12
SilentGhost

Puisqu'il n'y a aucun moyen de déterminer la longueur de toutes les lignes sans les lire, vous n'avez d'autre choix que de parcourir toutes les lignes avant votre ligne de départ. Tout ce que vous pouvez faire est de lui donner une apparence agréable. Si le fichier est vraiment énorme, vous pouvez utiliser une approche basée sur un générateur:

from itertools import dropwhile

def iterate_from_line(f, start_from_line):
    return (l for i, l in dropwhile(lambda x: x[0] < start_from_line, enumerate(f)))

for line in iterate_from_line(open(filename, "r", 0), 141978):
    DoSomethingWithThisLine(line)

Remarque: l'indice est basé sur zéro dans cette approche.

4
unbeknown

Je suis surpris que personne ne mentionne Islice

line = next(itertools.islice(Fhandle,index_of_interest,index_of_interest+1),None) # just the one line

ou si vous voulez tout le reste du fichier

rest_of_file = itertools.islice(Fhandle,index_of_interest)
for line in rest_of_file:
    print line

ou si vous voulez chaque ligne du fichier

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2)
for odd_line in rest_of_file:
    print odd_line
3
Joran Beasley

Si vous connaissez à l'avance la position dans le fichier (plutôt le numéro de ligne), vous pouvez utiliser file.seek () pour aller à cette position.

Edit: vous pouvez utiliser la fonction linecache.getline (nomfichier, lineno) , qui renverra le contenu de la ligne lineno, mais uniquement après avoir lu le fichier entier en mémoire. Bon si vous accédez de façon aléatoire aux lignes à partir du fichier (comme python lui-même pourrait vouloir faire pour imprimer une trace), mais pas bon pour un fichier de 15 Mo.

2
Noah

Qu'est-ce qui génère le fichier que vous voulez traiter? Si c'est quelque chose sous votre contrôle, vous pouvez générer un index (quelle ligne correspond à quelle position.) Au moment de l'ajout du fichier. Le fichier d'index peut avoir une taille de ligne fixe (espace ou 0 chiffre) et sera certainement plus petit. Et peut donc être lu et traité rapidement. 

  • Quelle ligne voulez-vous? 
  • Calculer le décalage d'octet du numéro de ligne correspondant dans le fichier d'index (possible car la taille de ligne du fichier d'index est constante).
  • Utilisez search ou quoi que ce soit pour sauter directement dans le fichier d’index.
  • Parse pour obtenir un décalage d'octet pour la ligne correspondante du fichier réel.
2
kamathln

Si vous ne souhaitez pas lire l'intégralité du fichier en mémoire, vous devrez peut-être utiliser un format autre que le texte brut.

bien sûr, tout dépend de ce que vous essayez de faire et de la fréquence à laquelle vous allez parcourir le fichier.

Par exemple, si vous allez sauter aux lignes plusieurs fois dans le même fichier et que vous savez que le fichier ne change pas lorsqu'il est utilisé, procédez comme suit:
Tout d’abord, parcourez l’ensemble du fichier et enregistrez "l’emplacement de recherche" de certains numéros de lignes de clés (par exemple, jamais moins de 1000 lignes),
Ensuite, si vous voulez la ligne 12005, passez à la position 12000 (que vous avez enregistrée), puis lisez 5 lignes et vous saurez que vous êtes sur la ligne 12005 , Etc., etc.

2
hasen

J'ai eu le même problème (besoin de récupérer d'énormes lignes spécifiques au fichier).

Bien sûr, je peux à chaque fois parcourir tous les enregistrements du fichier et l'arrêter lorsque le compteur sera égal à la ligne cible, mais cela ne fonctionne pas efficacement dans le cas où vous souhaitez obtenir plusieurs lignes spécifiques. Cela a résolu le problème principal - comment gérer directement à la place nécessaire du fichier.

J'ai découvert la décision suivante:.

t = open(file,’r’)
dict_pos = {}

kolvo = 0
length = 0
for each in t:
    dict_pos[kolvo] = length
    length = length+len(each)
    kolvo = kolvo+1

en fin de compte, la fonction de visée:

def give_line(line_number):
    t.seek(dict_pos.get(line_number))
    line = t.readline()
    return line

t.seek (line_number) - commande qui exécute l’élagage du fichier jusqu’à la création de la ligne. .__ Donc, si vous validez ensuite readline - vous obtenez votre ligne cible.

En utilisant une telle approche, j'ai économisé une partie importante du temps. 

2
user3810114

Les lignes elles-mêmes contiennent-elles des informations d'index? Si le contenu de chaque ligne était quelque chose comme "<line index>:Data", l'approche seek() pourrait alors être utilisée pour effectuer une recherche binaire dans le fichier, même si le montant de Data est variable. Vous voudriez chercher jusqu'au milieu du fichier, lire une ligne, vérifier si son index est supérieur ou inférieur à celui que vous voulez, etc.

Autrement, le mieux que vous puissiez faire est simplement readlines(). Si vous ne voulez pas lire tous les 15 Mo, vous pouvez utiliser l'argument sizehint pour remplacer au moins un grand nombre de readline()s par un nombre plus petit d'appels à readlines().

1
DNS

Si vous utilisez un fichier text & basé sur linux system , utilisez les commandes linux. 
Pour moi, cela a bien fonctionné!

import commands

def read_line(path, line=1):
    return commands.getoutput('head -%s %s | tail -1' % (line, path))

line_to_jump = 141978
read_line("path_to_large_text_file", line_to_jump)
0
HongKun Yoo

Vous pouvez utiliser mmap pour trouver le décalage des lignes. MMap semble être le moyen le plus rapide de traiter un fichier

exemple:

with open('input_file', "r+b") as f:
    mapped = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    i = 1
    for line in iter(mapped.readline, ""):
        if i == Line_I_want_to_jump:
            offsets = mapped.tell()
        i+=1

puis utilisez f.seek (décalages) pour passer à la ligne dont vous avez besoin

0
george

Voici un exemple utilisant 'readlines (sizehint)' pour lire un bloc de lignes à la fois. DNS a souligné cette solution. J'ai écrit cet exemple parce que les autres exemples ici sont orientés sur une seule ligne.

def getlineno(filename, lineno):
    if lineno < 1:
        raise TypeError("First line is line 1")
    f = open(filename)
    lines_read = 0
    while 1:
        lines = f.readlines(100000)
        if not lines:
            return None
        if lines_read + len(lines) >= lineno:
            return lines[lineno-lines_read-1]
        lines_read += len(lines)

print getlineno("nci_09425001_09450000.smi", 12000)
0
Andrew Dalke