¿Cuál es la contraparte perfecta en Python para "mientras no EOF"?

114

Para leer algún archivo de texto, en C o Pascal, siempre uso los siguientes fragmentos para leer los datos hasta EOF:

while not eof do begin
  readline(a);
  do_something;
end;

Por lo tanto, me pregunto cómo puedo hacer esto simple y rápido en Python.

Allen Koo
fuente

Respuestas:

189

Recorra el archivo para leer las líneas:

with open('somefile') as openfileobject:
    for line in openfileobject:
        do_something()

Los objetos de archivo son iterables y producen líneas hasta EOF. El uso del objeto de archivo como iterable utiliza un búfer para garantizar lecturas eficaces.

Puede hacer lo mismo con el stdin (no es necesario usar raw_input():

import sys

for line in sys.stdin:
    do_something()

Para completar la imagen, las lecturas binarias se pueden realizar con:

from functools import partial

with open('somefile', 'rb') as openfileobject:
    for chunk in iter(partial(openfileobject.read, 1024), b''):
        do_something()

where chunkcontendrá hasta 1024 bytes a la vez del archivo, y la iteración se detiene cuando openfileobject.read(1024)comienza a devolver cadenas de bytes vacías.

Martijn Pieters
fuente
4
Nota: El linetendrá un carácter de nueva línea al final.
ben_joseph
1
Leer líneas es un poco peligroso para archivos binarios genéricos, porque tal vez tenga una línea larga de 6GiB ...
LtWorf
@LtWorf: es por eso que muestro cómo leer archivos binarios en fragmentos en lugar de líneas.
Martijn Pieters
Estoy leyendo de un stdinproceso en ejecución ... por lo que nunca tendrá EOF hasta que mate el proceso. Pero luego llego al "final hasta ahora" y me estanco. ¿Cómo detecto esto y no un punto muerto? Como si no hubiera nuevas líneas, deja de leer los archivos (incluso si no hay un EOF, que en mi caso nunca existirá).
Charlie Parker
@CharlieParker: si llegó a un punto muerto, es probable que algo se olvide de vaciar un búfer. Sin un MCVE real, es difícil decir algo más que eso.
Martijn Pieters
61

Puede imitar el lenguaje C en Python.

Para leer un búfer hasta un max_sizenúmero de bytes, puede hacer esto:

with open(filename, 'rb') as f:
    while True:
        buf = f.read(max_size)
        if not buf:
            break
        process(buf)

O bien, un archivo de texto línea por línea:

# warning -- not idiomatic Python! See below...
with open(filename, 'rb') as f:
    while True:
        line = f.readline()
        if not line:
            break
        process(line)

Debe usar la while True / breakconstrucción, ya que no hay ninguna prueba eof en Python que no sea la falta de bytes devueltos por una lectura.

En C, es posible que tenga:

while ((ch != '\n') && (ch != EOF)) {
   // read the next ch and add to a buffer
   // ..
}

Sin embargo, no puede tener esto en Python:

 while (line = f.readline()):
     # syntax error

porque las asignaciones no están permitidas en expresiones en Python (aunque las versiones recientes de Python pueden imitar esto usando expresiones de asignación, ver más abajo).

Sin duda, es más idiomático en Python hacer esto:

# THIS IS IDIOMATIC Python. Do this:
with open('somefile') as f:
    for line in f:
        process(line)

Actualización: desde Python 3.8 también puede usar expresiones de asignación :

 while line := f.readline():
     process(line)
perro
fuente
@MartijnPieters: Ahora sí :-)
dawg
3
Como programador de C y Perl, su punto de que no se permiten asignaciones en expresiones fue crucial para mí.
LECTURA DE CÓDIGO
1
El método "while True:" también es útil cuando necesitas operar en más de una línea de entrada por iteración, algo que el idiomático Python no permite (hasta donde yo sé, de todos modos).
Donald Smith
No debería leer líneas si no hace suposiciones en el archivo. Un archivo binario puede tener líneas enormes ...
LtWorf
Parece que hay una ventaja en la forma no idiomática readline(): puede hacer un manejo de errores de grano fino, como capturar UnicodeDecodeError, lo que no puede hacer con la foriteración idiomática .
flow2k
17

El lenguaje de Python para abrir un archivo y leerlo línea por línea es:

with open('filename') as f:
    for line in f:
        do_something(line)

El archivo se cerrará automáticamente al final del código anterior (la withconstrucción se encarga de eso).

Finalmente, vale la pena señalar que linepreservará la nueva línea final. Esto se puede eliminar fácilmente usando:

line = line.rstrip()
NPE
fuente
1
+1, también señalando al OP que esto no es lo mismo que muy similar for line in f.readlines(): ..., una solución comúnmente sugerida.
Jedwards
12

Puede usar el siguiente fragmento de código para leer línea por línea, hasta el final del archivo

line = obj.readline()
while(line != ''):

    # Do Something

    line = obj.readline()
Arkansas
fuente
1
En mi opinión, esta es la respuesta que mejor refleja lo que se preguntó.
gvrocha
A menudo, iterar sobre las líneas distorsionaría la estructura del programa. Por ejemplo, en un analizador de idiomas, desea leer las líneas y procesarlas en secuencia. No desea reestructurar el nivel superior solo para poder repetir las líneas de lectura y luego enviarlas al analizador.
Jonathan Starr
11

Si bien hay sugerencias anteriores para "hacerlo a la manera de Python", si uno realmente quiere tener una lógica basada en EOF, supongo que usar el manejo de excepciones es la forma de hacerlo:

try:
    line = raw_input()
    ... whatever needs to be done incase of no EOF ...
except EOFError:
    ... whatever needs to be done incase of EOF ...

Ejemplo:

$ echo test | python -c "while True: print raw_input()"
test
Traceback (most recent call last):
  File "<string>", line 1, in <module> 
EOFError: EOF when reading a line

O presione Ctrl-Zcuando se le raw_input()solicite (Windows, Ctrl-ZLinux)

usuario5472996
fuente
@TessellatesHeckler eso no es lo que dice la documentación : "Se genera cuando una de las funciones integradas (input () o raw_input ()) alcanza una condición de fin de archivo (EOF) sin leer ningún dato".
Tadhg McDonald-Jensen
1
@ TadhgMcDonald-Jensen Bueno, oye, así será. Que extraño. Reclamo falso retirado y voto negativo injusto eliminado.
TessellatesHeckler
1

Puede utilizar el siguiente fragmento de código. readlines () lee todo el archivo a la vez y lo divide por línea.

line = obj.readlines()
Aditeya Pandey
fuente
0

Además de la gran respuesta de @ dawg, la solución equivalente usando el operador de morsa (Python> = 3.8):

with open(filename, 'rb') as f:
    while buf := f.read(max_size):
        process(buf)
infinito
fuente