Existe-t-il un moyen simple de savoir sur quel numéro de ligne se trouve un pointeur de fichier?

StackOverflow https://stackoverflow.com/questions/6367051

  •  28-10-2019
  •  | 
  •  

Question

Dans Python 2.5, je lis un fichier de données texte structuré (~ 30 Mo de taille) à l'aide d'un pointeur de fichier:

fp = open('myfile.txt', 'r')
line = fp.readline()
# ... many other fp.readline() processing steps, which
# are used in different contexts to read the structures

Mais ensuite, en analysant le fichier, j'ai trouvé quelque chose d'intéressant dont je veux signaler le numéro de ligne, afin que je puisse étudier le fichier dans un éditeur de texte.Je peux utiliser fp.tell() pour me dire où se trouve le décalage d'octet (par exemple, 16548974L), mais il n'y a pas de "fp.tell_line_number ()" pour m'aider à traduire cela en un numéro de ligne.

Existe-t-il un Python intégré ou une extension pour facilement suivre et "dire" sur quel numéro de ligne se trouve un pointeur de fichier texte?

Remarque: je suis ne pas demander à d'utiliser un compteur de style line_number += 1, comme j'appelle fp.readline() dans différents contextes et cette approche nécessiterait plus de débogage qu'il ne vaut la peine d'insérer le compteur dans les coins droits du code.

Était-ce utile?

La solution

Une solution typique à ce problème est de définir une nouvelle classe qui encapsule une instance existante d'un file, qui compte automatiquement les nombres.Quelque chose comme ça (juste du haut de ma tête, je n'ai pas testé ça):

class FileLineWrapper(object):
    def __init__(self, f):
        self.f = f
        self.line = 0
    def close(self):
        return self.f.close()
    def readline(self):
        self.line += 1
        return self.f.readline()
    # to allow using in 'with' statements 
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

Utilisez-le comme ceci:

f = FileLineWrapper(open("myfile.txt", "r"))
f.readline()
print(f.line)

Il semble que le module standard fileinput fait à peu près la même chose (et d'autreschoses aussi);vous pouvez l'utiliser à la place si vous le souhaitez.

Autres conseils

Le module fileinput pourrait vous être utile.Il fournit une interface généralisée pour itérer sur un nombre arbitraire de fichiers.Quelques faits saillants pertinents de la documentation:

fileinput.lineno()

Renvoie le numéro de ligne cumulé de la ligne qui vient d'être lue.Avant que la première ligne n'ait été lue, renvoie 0. Une fois que la dernière ligne du dernier fichier a été lue, renvoie le numéro de ligne de cette ligne.

fileinput.filelineno()

Renvoie le numéro de ligne dans le fichier courant.Avant que la première ligne n'ait été lue, renvoie 0. Une fois que la dernière ligne du dernier fichier a été lue, renvoie le numéro de ligne de cette ligne dans le fichier.

Le code suivant affichera le numéro de ligne (où le pointeur est actuellement) tout en parcourant le fichier ('testfile')

file=open("testfile", "r")
for line_no, line in enumerate(file):
    print line_no     # The content of the line is in variable 'line'
file.close()

< gagnantoutput :

1
2
3
...

Je ne pense pas, pas comme vous le souhaitez (comme dans une fonctionnalité intégrée standard des descripteurs de fichiers Python renvoyés par open).

Si vous n'êtes pas disposé à suivre le numéro de ligne manuellement lorsque vous lisez les lignes ou à utiliser une classe wrapper (excellentes suggestions de GregH et de l'expéditeur, au fait), alors je pense que vous devrez simplement utiliserla figure fp.tell() et revenez au début du fichier, en lisant jusqu'à ce que vous y arriviez.

Ce n'est pas trop une mauvaise option car je suppose que les conditions d'erreur seront moins probables que tout fonctionnant à merveille.Si tout fonctionne correctement, il n'y a aucun impact.

S'il y a une erreur, vous avez l'effort supplémentaire de réanalyser le fichier.Si le fichier est volumineux , cela peut avoir un impact sur vos performances perçues - vous devez en tenir compte s'il s'agit d'un problème.

Une façon pourrait être d'itérer sur la ligne et de garder un compte explicite du nombre de lignes déjà vues:

>>> f=open('text.txt','r')
>>> from itertools import izip
>>> from itertools import count
>>> f=open('test.java','r')
>>> for line_no,line in izip(count(),f):
...     print line_no,line

Le code suivant crée une fonction Which_Line_for_Position (pos) qui donne le numéro de la ligne pour la position pos , c'est-à-dire le numéro de ligne dans lequel se trouve le caractère situé à la position pos dans le fichier.

Cette fonction peut être utilisée avec n'importe quelle position comme argument, indépendamment de la valeur de la position actuelle du pointeur du fichier et de l'historique des mouvements de ce pointeur avant l'appel de la fonction.

Donc, avec cette fonction, on n'est pas limité à déterminer le numéro de la ligne courante uniquement lors d'une itération ininterrompue sur les lignes, comme c'est le cas avec la solution de Greg Hewgill.

with open(filepath,'rb') as f:
    GIVE_NO_FOR_END = {}
    end = 0
    for i,line in enumerate(f):
        end += len(line)
        GIVE_NO_FOR_END[end] = i
    if line[-1]=='\n':
        GIVE_NO_FOR_END[end+1] = i+1
    end_positions = GIVE_NO_FOR_END.keys()
    end_positions.sort()

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

.

La même solution peut être écrite à l'aide du module fileinput :

import fileinput

GIVE_NO_FOR_END = {}
end = 0
for line in fileinput.input(filepath,'rb'):
    end += len(line)
    GIVE_NO_FOR_END[end] = fileinput.filelineno()
if line[-1]=='\n':
    GIVE_NO_FOR_END[end+1] = fileinput.filelineno()+1
fileinput.close()

end_positions = GIVE_NO_FOR_END.keys()
end_positions.sort()

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

Mais cette solution présente quelques inconvénients:

  • il doit importer le module fileinput
  • il supprime tout le contenu du fichier !! Il doit y avoir quelque chose qui ne va pas dans mon code, mais je ne connais pas assez fileinput pour le trouver. Ou est-ce un comportement normal de la fonction fileinput.input () ?
  • il semble que le fichier soit d'abord entièrement lu avant qu'une itération puisse être lancée. Si tel est le cas, pour un fichier très volumineux, la taille du fichier peut dépasser la capacité de la RAM. Je ne suis pas sûr de ce point: j'ai essayé de tester avec un fichier de 1,5 Go mais c'est assez long et j'ai laissé tomber ce point pour le moment. Si ce point est juste, cela constitue un argument pour utiliser l'autre solution avec enumerate()

.

exemple:

text = '''Harold Acton (1904–1994)
Gilbert Adair (born 1944)
Helen Adam (1909–1993)
Arthur Henry Adams (1872–1936)
Robert Adamson (1852–1902)
Fleur Adcock (born 1934)
Joseph Addison (1672–1719)
Mark Akenside (1721–1770)
James Alexander Allan (1889–1956)
Leslie Holdsworthy Allen (1879–1964)
William Allingham (1824/28-1889)
Kingsley Amis (1922–1995)
Ethel Anderson (1883–1958)
Bruce Andrews (born 1948)
Maya Angelou (born 1928)
Rae Armantrout (born 1947)
Simon Armitage (born 1963)
Matthew Arnold (1822–1888)
John Ashbery (born 1927)
Thomas Ashe (1836–1889)
Thea Astley (1925–2004)
Edwin Atherstone (1788–1872)'''


#with open('alao.txt','rb') as f:

f = text.splitlines(True)
# argument True in splitlines() makes the newlines kept

GIVE_NO_FOR_END = {}
end = 0
for i,line in enumerate(f):
    end += len(line)
    GIVE_NO_FOR_END[end] = i
if line[-1]=='\n':
    GIVE_NO_FOR_END[end+1] = i+1
end_positions = GIVE_NO_FOR_END.keys()
end_positions.sort()


print '\n'.join('line %-3s  ending at position %s' % (str(GIVE_NO_FOR_END[end]),str(end))
                for end in end_positions)

def Which_Line_for_Position(pos,
                            dic = GIVE_NO_FOR_END,
                            keys = end_positions,
                            kmax = end_positions[-1]):
    return dic[(k for k in keys if pos < k).next()] if pos<kmax else None

print
for x in (2,450,320,104,105,599,600):
    print 'pos=%-6s   line %s' % (x,Which_Line_for_Position(x))

résultat

line 0    ending at position 25
line 1    ending at position 51
line 2    ending at position 74
line 3    ending at position 105
line 4    ending at position 132
line 5    ending at position 157
line 6    ending at position 184
line 7    ending at position 210
line 8    ending at position 244
line 9    ending at position 281
line 10   ending at position 314
line 11   ending at position 340
line 12   ending at position 367
line 13   ending at position 393
line 14   ending at position 418
line 15   ending at position 445
line 16   ending at position 472
line 17   ending at position 499
line 18   ending at position 524
line 19   ending at position 548
line 20   ending at position 572
line 21   ending at position 600

pos=2        line 0
pos=450      line 16
pos=320      line 11
pos=104      line 3
pos=105      line 4
pos=599      line 21
pos=600      line None

.

Ensuite, avec la fonction Which_Line_for_Position () , il est facile d'obtenir le numéro d'une ligne courante: il suffit de passer f.tell () comme argument à la fonction

Mais ATTENTION : lorsque vous utilisez f.tell () et que vous faites des mouvements du pointeur du fichier dans le fichier, il faut absolument que le fichier soit ouvert en mode binaire : 'rb' ou 'rb +' ou 'ab' ou ....

Récemment, je suis tombé sur un problème similaire et j'ai trouvé cette solution basée sur les classes.

class TextFileProcessor(object):

    def __init__(self, path_to_file):
        self.print_line_mod_number = 0
        self.__path_to_file = path_to_file
        self.__line_number = 0

    def __printLineNumberMod(self):
        if self.print_line_mod_number != 0:
            if self.__line_number % self.print_line_mod_number == 0:
                print(self.__line_number)

    def processFile(self):
        with open(self.__path_to_file, 'r', encoding='utf-8') as text_file:
            for self.__line_number, line in enumerate(text_file, start=1):
                self.__printLineNumberMod()

                # do some stuff with line here.

Définissez la propriété print_line_mod_number sur la cadence que vous souhaitez enregistrer, puis appelez processFile.

Par exemple ... si vous voulez des commentaires toutes les 100 lignes, cela ressemblerait à ceci.

tfp = TextFileProcessor('C:\\myfile.txt')
tfp.print_line_mod_number = 100
tfp.processFile()

La sortie de la console serait

100
200
300
400
etc...

Concernant la solution de @eyquem , je suggère d'utiliser mode='r' avec le module fileinput et l'option fileinput.lineno() et cela a fonctionné pourmoi.

Voici comment j'implémente ces options dans mon code.

    table=fileinput.input('largefile.txt',mode="r")
    if fileinput.lineno() >= stop : # you can disregard the IF condition but I am posting to illustrate the approach from my code.
           temp_out.close()
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top