117 votes

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

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 énorme fichier texte (~15MB) avec des lignes de longueur inconnue mais différente, et avoir besoin de sauter à une ligne particulière dont je connais le numéro à l'avance ? Je me sens mal de les traiter une par une alors que je sais que je peux ignorer au moins la première moitié du fichier. Je cherche une solution plus élégante, s'il en existe une.

0 votes

Comment sais-tu que la première moitié du fichier n'est pas un tas de " \n "s" alors que la seconde moitié est une seule ligne ? Pourquoi vous sentez-vous mal à ce sujet ?

9 votes

Je pense que le titre est trompeur - en effet, 15 Mo n'est pas vraiment un "énorme fichier texte", c'est le moins qu'on puisse dire...

5voto

Comme il n'y a aucun moyen de déterminer la longueur de toutes les lignes sans les lire, vous n'avez pas d'autre choix que d'itérer sur toutes les lignes avant votre ligne de départ. Tout ce que vous pouvez faire, c'est de rendre le tout agréable à regarder. 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.

4voto

hasenj Points 36139

Si vous ne souhaitez pas lire l'intégralité du fichier en mémoire, il vous faudra peut-être trouver un autre format 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 traverser le fichier.

Par exemple, si vous allez sauter aux lignes plusieurs fois dans le même fichier, et vous savez que le fichier ne change pas pendant que vous travaillez avec lui, vous pouvez le faire :
Tout d'abord, parcourez l'ensemble du fichier, et enregistrez la "position de recherche" de certains numéros de lignes clés (par exemple, toutes les 1000 lignes),
Ensuite, si vous voulez la ligne 12005, sautez à la position 12000 (que vous avez enregistrée) puis lisez 5 lignes et vous saurez que vous êtes à la ligne 12005. et ainsi de suite

4voto

george Points 843

Vous pouvez utiliser mmap pour trouver l'offset des lignes. MMap semble être la manière la 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(offsets) pour vous déplacer sur la ligne dont vous avez besoin.

4voto

Joseph Catrambone Points 143

Aucune des réponses n'est particulièrement satisfaisante, alors voici un petit extrait pour vous aider.

class LineSeekableFile:
    def __init__(self, seekable):
        self.fin = seekable
        self.line_map = list() # Map from line index -> file position.
        self.line_map.append(0)
        while seekable.readline():
            self.line_map.append(seekable.tell())

    def __getitem__(self, index):
        # NOTE: This assumes that you're not reading the file sequentially.  
        # For that, just use 'for line in file'.
        self.fin.seek(self.line_map[index])
        return self.fin.readline()

Exemple d'utilisation :

In: !cat /tmp/test.txt

Out:
Line zero.
Line one!

Line three.
End of file, line four.

In:
with open("/tmp/test.txt", 'rt') as fin:
    seeker = LineSeekableFile(fin)    
    print(seeker[1])
Out:
Line one!

Cela implique de faire beaucoup de recherches dans le fichier, mais c'est utile dans les cas où vous ne pouvez pas mettre tout le fichier en mémoire. Il effectue une lecture initiale pour obtenir les emplacements des lignes (il lit donc le fichier entier, mais ne le garde pas en mémoire), puis chaque accès effectue une recherche de fichier après coup.

Je propose l'extrait ci-dessus sous la licence MIT ou Apache, à la discrétion de l'utilisateur.

2 votes

C'est la meilleure solution, non seulement pour cette question, mais aussi pour de nombreux autres problèmes liés à la mémoire lors de la lecture de fichiers volumineux. Merci pour cela !

3voto

Noah Points 3398

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

Modifier : vous pouvez utiliser le linecache.getline(nom du fichier, lineno) qui renverra le contenu de la ligne lineno, mais seulement après avoir lu le fichier entier en mémoire. C'est une bonne chose si vous accédez de façon aléatoire à des lignes du fichier (comme python lui-même pourrait vouloir le faire pour imprimer un retour de trace), mais pas pour un fichier de 15MB.

0 votes

Je n'utiliserais certainement pas linecache dans ce but, car il lit tout le fichier en mémoire avant de retourner la ligne demandée.

0 votes

Oui, ça semblait trop beau pour être vrai. J'aimerais toujours qu'il y ait un module pour faire cela efficacement, mais j'ai tendance à utiliser la méthode file.seek() à la place.

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