¿Cómo puedo leer un solo carácter a la vez de un archivo en Python?

7 minutos de lectura

avatar de usuario de kaushik
Kaushik

En Python, dado el nombre de un archivo, ¿cómo puedo escribir un bucle que lea un carácter cada vez que pasa por el bucle?

avatar de usuario de jchl
jchl

with open(filename) as f:
    while True:
        c = f.read(1)
        if not c:
            print("End of file")
            break
        print("Read a character:", c)

  • Dado que esto está leyendo un byte a la vez, ¿no fallará para las codificaciones que no son ASCII?

    –David Chouinard

    15/01/2013 a las 18:30

  • Las preguntas y respuestas son conceptos confusos de caracteres y bytes. Si el archivo está en una codificación de un solo byte por carácter, como Ascii y muchos otros, entonces sí, está leyendo un solo carácter al leer un fragmento del tamaño de un solo byte; de ​​lo contrario, si la codificación requiere más de un solo byte por carácter, entonces está simplemente leyendo un solo byte, no un solo carácter.

    – Basilea Shishani

    16 de octubre de 2013 a las 1:34

  • Así es. Por lo tanto, a menudo hago result = open(filename).read() y luego leer result personaje por personaje.

    – Shravan

    30 de junio de 2015 a las 17:27

  • A la pregunta de David Chouinard: este fragmento funciona correctamente en Python 3 con un archivo en codificación UTF-8. Si tiene, por ejemplo, un archivo con codificación Windows-1250, simplemente cambie la primera línea a with open(filename, encoding='Windows-1250') as f:

    – SergO

    21 de enero de 2016 a las 9:17

  • Y para agregar a SergO, open(filename, "r") contra open(filename, "rb") puede resultar en diferentes números de iteraciones (al menos hablando de Python 3). El modo “r” podría leer varios bytes para obtener c si golpea el carácter especial apropiado.

    – dc310

    8 de diciembre de 2017 a las 23:06


Avatar de usuario de Raj
Raj

Primero, abre un archivo:

with open("filename") as fileobj:
    for line in fileobj:  
       for ch in line: 
           print(ch)

Esto pasa por cada línea del archivo y luego por cada carácter de esa línea.

  • De acuerdo, esta parece ser la forma más pitónica de hacerlo. ¿No se encargaría esto de manejar la codificación que no es ASCII también?

    – Ron7

    28 de noviembre de 2017 a las 9:19


  • Una de las razones por las que puede leer un archivo de un carácter a la vez es que el archivo es demasiado grande para caber en la memoria. Pero la respuesta anterior asume que cada línea puede caber en la memoria.

    – CS

    11 de febrero de 2018 a las 22:44

  • Editado para que coincida con Python 3.

    usuario11462042

    8 de abril de 2020 a las 0:24

  • Dado que el OP nunca mencionó leer todo el archivo un carácter a la vez, este enfoque no es óptimo porque todo el archivo podría estar contenido en una sola línea; en cuyo caso se necesita un tiempo considerable para leer toda la línea antes de que se realice el procesamiento de caracteres. Es mejor usar f.read(1) en lecturas parciales en estos casos.

    – búho7

    7 de julio de 2021 a las 12:02


  • -1. Secundando el comentario de @CS. El OP preguntó cómo leer “un solo carácter a la vez”, por lo que esto no responde la pregunta. Esto no es más simple que la respuesta aceptada, y es mejor tener una función que a veces no bloquee innecesariamente su script/aplicación. ¿Qué sucede si se trata de un INSERTO SQL para una tabla completa? ¿O usa un carácter de nueva línea no nativo? El mejor de los casos es un almacenamiento en búfer ineficiente; el peor de los casos se está quedando sin memoria.

    –Douglas Myers-Turnbull

    13 de julio de 2021 a las 20:46


Me gusta la respuesta aceptada: es sencillo y hará el trabajo. También me gustaría ofrecer una implementación alternativa:

def chunks(filename, buffer_size=4096):
    """Reads `filename` in chunks of `buffer_size` bytes and yields each chunk
    until no more characters can be read; the last chunk will most likely have
    less than `buffer_size` bytes.

    :param str filename: Path to the file
    :param int buffer_size: Buffer size, in bytes (default is 4096)
    :return: Yields chunks of `buffer_size` size until exhausting the file
    :rtype: str

    """
    with open(filename, "rb") as fp:
        chunk = fp.read(buffer_size)
        while chunk:
            yield chunk
            chunk = fp.read(buffer_size)

def chars(filename, buffersize=4096):
    """Yields the contents of file `filename` character-by-character. Warning:
    will only work for encodings where one character is encoded as one byte.

    :param str filename: Path to the file
    :param int buffer_size: Buffer size for the underlying chunks,
    in bytes (default is 4096)
    :return: Yields the contents of `filename` character-by-character.
    :rtype: char

    """
    for chunk in chunks(filename, buffersize):
        for char in chunk:
            yield char

def main(buffersize, filenames):
    """Reads several files character by character and redirects their contents
    to `/dev/null`.

    """
    for filename in filenames:
        with open("/dev/null", "wb") as fp:
            for char in chars(filename, buffersize):
                fp.write(char)

if __name__ == "__main__":
    # Try reading several files varying the buffer size
    import sys
    buffersize = int(sys.argv[1])
    filenames  = sys.argv[2:]
    sys.exit(main(buffersize, filenames))

El código que sugiero es esencialmente la misma idea que su respuesta aceptada: lea una cantidad determinada de bytes del archivo. La diferencia es que primero lee una buena parte de los datos (4006 es un buen valor predeterminado para X86, pero es posible que desee probar 1024 u 8192; cualquier múltiplo del tamaño de su página), y luego produce los caracteres en esa parte. por uno.

El código que presento puede ser más rápido para archivos más grandes. Tomar como ejemplo, el texto completo de Guerra y paz, de Tolstoi. Estos son mis resultados de sincronización (Mac Book Pro con OS X 10.7.4; so.py es el nombre que le di al código que pegué):

$ time python so.py 1 2600.txt.utf-8
python so.py 1 2600.txt.utf-8  3.79s user 0.01s system 99% cpu 3.808 total
$ time python so.py 4096 2600.txt.utf-8
python so.py 4096 2600.txt.utf-8  1.31s user 0.01s system 99% cpu 1.318 total

Ahora: no tome el tamaño del búfer en 4096 como una verdad universal; mire los resultados que obtengo para diferentes tamaños (tamaño de búfer (bytes) frente a tiempo de pared (seg)):

   2 2.726 
   4 1.948 
   8 1.693 
  16 1.534 
  32 1.525 
  64 1.398 
 128 1.432 
 256 1.377 
 512 1.347 
1024 1.442 
2048 1.316 
4096 1.318 

Como puede ver, puede comenzar a ver ganancias antes (y es probable que mis tiempos sean muy inexactos); el tamaño del búfer es una compensación entre el rendimiento y la memoria. El valor predeterminado de 4096 es solo una opción razonable pero, como siempre, mida primero.

avatar de usuario de joaquín
joaquín

Justo:

myfile = open(filename)
onecharacter = myfile.read(1)

Avatar de usuario de Mattias Nilsson
matias nilsson

Python mismo puede ayudarte con esto, en modo interactivo:

>>> help(file.read)
Help on method_descriptor:

read(...)
    read([size]) -> read at most size bytes, returned as a string.

    If the size argument is negative or omitted, read until EOF is reached.
    Notice that when in non-blocking mode, less data than what was requested
    may be returned, even if no size parameter was given.

  • Estoy de acuerdo con el sentimiento, pero ¿quizás esto sea más adecuado como comentario para el OP?

    – Mike Bóers

    7 de junio de 2010 a las 12:56

  • Podría ser, pero creo que todo ese texto se vería desordenado en un comentario.

    –Mattias Nilsson

    7 de junio de 2010 a las 13:23

Avatar de usuario de Michael Kropat
Michael Kropat

Hoy aprendí un nuevo modismo para esto mientras miraba la película de Raymond Hettinger. Transformando el código en Python hermoso e idiomático:

import functools

with open(filename) as f:
    f_read_ch = functools.partial(f.read, 1)
    for ch in iter(f_read_ch, ''):
        print 'Read a character:', repr(ch) 

  • Estoy de acuerdo con el sentimiento, pero ¿quizás esto sea más adecuado como comentario para el OP?

    – Mike Bóers

    7 de junio de 2010 a las 12:56

  • Podría ser, pero creo que todo ese texto se vería desordenado en un comentario.

    –Mattias Nilsson

    7 de junio de 2010 a las 13:23

Avatar de usuario de David Sykes
david sikes

Solo lee un solo carácter

f.read(1)

¿Ha sido útil esta solución?