2011-06-16 16 views
6

En Python 2.5, estoy leyendo un archivo de datos de texto estructurado (~ 30 MB de tamaño) utilizando un puntero de archivo:¿Hay alguna manera fácil de decir en qué línea está un puntero de archivo?

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

Pero entonces, al analizar el archivo, me golpeó algo interesante que quiero reportar el número de línea, por lo que puedo investigar el archivo en un editor de texto. Puedo usar fp.tell() para decirme dónde está el desplazamiento del byte (por ejemplo, 16548974L), pero no hay "fp.tell_line_number()" para ayudarme a traducir esto a un número de línea.

¿Hay una extensión o un built-in de Python para rastrear fácilmente y "decir" en qué número de línea está el puntero de un archivo de texto?

Nota: Estoy not asking utilizar un contador line_number += 1 estilo, como lo llamo fp.readline() en diferentes contextos y que el enfoque requeriría más de depuración de lo que vale para insertar el contador en las esquinas derecha del código.

Respuesta

13

Una solución típica a este problema es definir una nueva clase que envuelve una instancia existente de file, que cuenta automáticamente los números. Algo como esto (justo al lado de la parte superior de mi cabeza, no he probado esto):

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() 

utilizar de esta manera:

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

Parece que el módulo estándar fileinput hace lo mismo cosa (y algunas otras cosas también); podrías usar eso si quieres.

+0

+1, una buena solución simple ya que sólo requiere la 'open' llamada a ser cambiado. Es probable que desee proporcionar envoltorios para cualquier otra función que se utilice también (como 'cerrar'), pero deben ser funciones de transferencia bastante menores. – paxdiablo

+0

Oh bien, 'cerrar' es útil, lo agregaré. –

+0

Ambas soluciones funcionan genial, ¡fantástico! –

1

No lo creo, no de la manera que usted desea (como en una característica incorporada estándar de los identificadores de archivos Python devueltos por open).

Si no puede seguir el número de línea manualmente mientras lee las líneas o el uso de una clase contenedora (sugerencias excelentes de GregH y senderle, dicho sea de paso), entonces creo que tendrá que simplemente use la figura fp.tell() y regrese al inicio del archivo, leyendo hasta llegar allí.

Eso no es too mala opción ya que supongo que las condiciones de error serán menos probables que todo lo que funciona a la perfección. Si todo funciona bien, no hay impacto.

Si hay un error, entonces tiene el esfuerzo extra de volver a explorar el archivo. Si el archivo es grande, que puede afectar su rendimiento percibido, debe tener esto en cuenta si se trata de un problema.

10

Puede que le resulte útil el módulo fileinput. Proporciona una interfaz general para iterar sobre una cantidad arbitraria de archivos. Algunos puntos relevantes de los documentos:

fileinput.lineno()

devolver el número de línea acumulada de la línea que se acaba de leer. Antes de leer la primera línea, devuelve 0. Después de que se haya leído la última línea del último archivo, devuelve el número de línea de esa línea.

fileinput.filelineno()

retorno el número de línea en el archivo actual. Antes de leer la primera línea, devuelve 0. Después de que se haya leído la última línea del último archivo, devuelve el número de línea de esa línea dentro del archivo.

+0

pequeña advertencia:' fileinput' no parece ser compatible con 'con 'declaraciones en Python2.7 ... – Julien

0

Una forma podría ser la de iterar sobre la línea y mantener un recuento explícito del número de líneas ya se ha visto:

>>> 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 
0

El siguiente código crea una función Which_Line_for_Position (pos) que da la número de la línea para la posición pos, es decir el número de la línea en la que se encuentra el carácter situado en la posición pos en el archivo.

Esta función se puede utilizar con cualquier posición como argumento, independientemente del valor de la posición actual del puntero del archivo y del histórico de los movimientos de este puntero antes de llamar a la función.

Por lo tanto, con esta función, uno no se limita a determinar el número de la línea actual solo durante una iteración ininterrumpida en las líneas, como es el caso de la solución 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 misma solución se puede escribir con la ayuda del módulo 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 

Pero esta solución tiene algunos inconvenientes:

  • necesita importar el módulo fileinput
  • ¡borra todo el contenido del archivo! Debe haber algo mal en mi código pero no sé fileinput suficiente para encontrarlo. ¿O es un comportamiento normal de fileinput.input() función?
  • Parece que el archivo se lee por completo antes de que se pueda iniciar cualquier iteración. Si es así, para un archivo muy grande, el tamaño del archivo puede exceder la capacidad de la RAM. No estoy seguro de este punto: traté de probar con un archivo de 1,5 GB, pero es bastante largo y dejé este punto por el momento. Si este punto es correcto, constituye un argumento para utilizar la otra solución con enumerate()

.

Ejemplo:

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)) 

resultado

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 

.

Entonces, que tiene una función Which_Line_for_Position(), es fácil obtener el número de una línea de corriente: de paso f.tell() como argumento a la función

Pero ADVERTENCIA: cuando utilizando f.tell() y haciendo movimientos de puntero del archivo en el archivo, es absolutamente necesario que el archivo se abre en modo binario: 'rb' o 'rb +' o 'ab' o ...

-1

Con respecto a solution by @eyquem, sugiero usar mode='r' con el módulo fileinput y la opción fileinput.lineno() y me ha funcionado.

Así es como estoy implementando estas opciones en mi código.

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() 
+1

Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar aclaraciones de un autor, deje un comentario debajo de su publicación; siempre puede comentar sus propias publicaciones, y una vez que tenga suficiente [reputación] (http://stackoverflow.com/help/whats-reputation) lo hará poder [comentar cualquier publicación] (http://stackoverflow.com/help/privileges/comment). - [De la crítica] (/ review/low-quality-posts/10786418) – Prune

+0

@ Prune - gracias por el comentario y he incluido un fragmento de código de mi aprendizaje para agregar claridad a lo que estaba sugiriendo. – speedchase

+0

Tenga en cuenta que "arriba" no tiene contexto entre las respuestas. Los votos de respuesta cambian y las respuestas se pueden ordenar de diferentes maneras. Mejor vincular a la respuesta a la que se refiere. – ale

7

El siguiente código imprime el número de línea (cuando el puntero se encuentra actualmente en) al atravesar a través del archivo ('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() 

de salida:

1 
2 
3 
... 
+0

Por favor siempre agregue alguna explicación a su código ... – andreas

+0

Gracias por la sugerencia. voy a –

0

Haciendo el tonto con un problema similar recientemente y se le ocurrió esta solución basada en la clase.

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. 

establecer la propiedad print_line_mod_number a la frecuencia que desee registrar y luego llamar processFile.

Por ejemplo ... si quiere recibir comentarios cada 100 líneas, se vería así.

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

La salida de la consola sería

100 
200 
300 
400 
etc... 
Cuestiones relacionadas