io en Python: domina streams y buffers

Publicado el: 24/08/2026
Tempo de leitura: 6 minutos
Vibrant green tree python elegantly coiled on branch, showcasing its natural beauty.

El módulo io define las interfaces centrales de entrada y salida de Python. Archivos abiertos con open(), buffers en memoria, wrappers de texto y muchos objetos entregados por sockets, compresión y bibliotecas externas siguen contratos basados en esta jerarquía.

Existen tres categorías principales: I/O de texto, I/O binario con buffering e I/O bruto. Comprender la diferencia evita errores de tipo, pérdida de datos por encoding, escrituras parciales, uso excesivo de memoria y sorpresas entre plataformas.

Streams y objetos file-like

Un stream es una secuencia de datos accesible mediante read(), write(), seek() y close(). Puede representar un archivo, memoria, pipe, socket, respuesta HTTP, contenido comprimido o implementación personalizada.

No todos los streams admiten todas las operaciones. Usa readable(), writable() y seekable() para consultar capacidades. Una operación no soportada puede lanzar io.UnsupportedOperation.

Texto y bytes son contratos distintos

Los streams de texto reciben y producen str. Los binarios reciben objetos bytes-like y producen bytes. Mezclar tipos genera TypeError.

with open("datos.txt", "w", encoding="utf-8") as archivo:
    archivo.write("Hola")

with open("imagen.png", "wb") as archivo:
    archivo.write(b"\x89PNG")

No uses modo texto para imágenes, ZIP, PDF o protocolos binarios. No uses modo binario para texto sin una política explícita de encoding y decoding.

Especifica el encoding

El encoding predeterminado de open() depende de la locale, salvo que UTF-8 Mode esté activo. Código que funciona en Linux puede fallar en Windows si omite encoding="utf-8".

with open("README.md", "r", encoding="utf-8") as archivo:
    contenido = archivo.read()

Usa encoding="locale" cuando la locale forma parte deliberada del formato. Para APIs nuevas, UTF-8 explícito suele ser más predecible. Consulta codecs en Python.

EncodingWarning

Python puede avisar cuando una API depende del encoding predeterminado. Ejecuta con -X warn_default_encoding o define PYTHONWARNDEFAULTENCODING. Las funciones que reciben encoding=None pueden usar io.text_encoding().

import io


def leer_texto(ruta, encoding=None):
    encoding = io.text_encoding(encoding)
    with open(ruta, encoding=encoding) as archivo:
        return archivo.read()

Traducción de newlines

El argumento newline controla los finales de línea. Con None, el modo universal reconoce \n, \r y \r\n y devuelve \n. Con string vacío, los reconoce pero los conserva. Un valor concreto restringe el terminador.

Para formatos que exigen bytes exactos, como firmas, hashes y protocolos, usa modo binario o configura newline de manera explícita.

Context managers y cierre

Los objetos de IOBase soportan with, garantizando el cierre incluso cuando ocurre una excepción.

with open("informe.txt", "w", encoding="utf-8") as archivo:
    archivo.write("resultado\n")

Las operaciones sobre un stream cerrado suelen lanzar ValueError. Llamar close() varias veces está permitido, pero el objeto no debe reutilizarse.

flush() no es fsync()

flush() envía el buffer de Python a la capa subyacente, pero no garantiza persistencia física. Para durabilidad, haz flush y llama a os.fsync(), comprendiendo el coste y las garantías de la plataforma.

import os

with open("estado.txt", "w", encoding="utf-8") as archivo:
    archivo.write("confirmado")
    archivo.flush()
    os.fsync(archivo.fileno())

Para actualizaciones atómicas, escribe en un archivo temporal del mismo filesystem y sustituye el destino después del éxito. Consulta tempfile en Python.

I/O bruto

RawIOBase representa acceso de bajo nivel a bytes. FileIO es la implementación de archivos. Las operaciones brutas pueden leer menos bytes que los solicitados o escribir solo parte del buffer.

import io

bruto = io.FileIO("datos.bin", "w")
try:
    restante = memoryview(b"contenido")
    while restante:
        cantidad = bruto.write(restante)
        if cantidad is None:
            continue
        restante = restante[cantidad:]
finally:
    bruto.close()

La mayoría de aplicaciones debería preferir streams buffered, que reintentan operaciones apropiadas y ofrecen un contrato más predecible.

BufferedReader y BufferedWriter

BufferedReader lee bloques mayores y guarda bytes para llamadas posteriores. BufferedWriter acumula salida y la envía cuando el buffer se llena, durante flush(), seek o cierre.

El tamaño predeterminado está en io.DEFAULT_BUFFER_SIZE, aunque open() puede considerar el block size. Mide throughput, memoria y latencia antes de modificarlo.

read(), read1() y readinto()

read(size) puede realizar varias lecturas brutas. read1(size) usa como máximo una llamada. readinto(buffer) llena memoria preasignada y reduce allocations.

buffer = bytearray(64 * 1024)
with open("grande.bin", "rb") as archivo:
    while cantidad := archivo.readinto(buffer):
        procesar(memoryview(buffer)[:cantidad])

No conserves la view después de reutilizar el buffer sin copiar los datos.

BytesIO

BytesIO es un stream binario en memoria.

import io

stream = io.BytesIO()
stream.write(b"cabecera")
stream.seek(0)
print(stream.read())

getvalue() devuelve todos los bytes. getbuffer() expone una view modificable sin copia. Mientras exista, el objeto no puede redimensionarse ni cerrarse.

StringIO

StringIO es un stream de texto Unicode en memoria, útil para tests, informes y captura de salida.

import io

salida = io.StringIO()
print("primera línea", file=salida)
print("segunda línea", file=salida)
texto = salida.getvalue()

Para simular append, usa seek(0, io.SEEK_END). Al cerrar, el buffer se descarta.

TextIOWrapper

TextIOWrapper envuelve un stream binario buffered y gestiona encoding, decoding y newlines.

import io

bruto = open("datos.txt", "rb", buffering=0)
buffer = io.BufferedReader(bruto)
texto = io.TextIOWrapper(buffer, encoding="utf-8", errors="strict")
try:
    print(texto.readline())
finally:
    texto.close()

open(..., encoding=...) construye estas capas automáticamente.

Políticas de errores de encoding

errors="strict" lanza excepción y debería ser el default cuando importa la integridad. ignore elimina datos silenciosamente y rara vez es apropiado. replace inserta un marcador. Otros handlers sirven para contextos concretos.

No uses ignore solo para hacer que un archivo abra. Corrige el encoding o adopta una política documentada.

reconfigure()

TextIOWrapper.reconfigure() cambia encoding, errors, newline, line buffering y write-through. Encoding y newline no pueden cambiar después de leer porque el decoder ya tiene estado.

import sys

sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace")

Cambiar un stream global afecta a todo el proceso. Hazlo solo durante la inicialización y respeta redirecciones.

seek() y tell() en texto

En binario, las posiciones son offsets de bytes. En texto, tell() devuelve un cookie opaco que incluye estado del decoder. Solo pasa valores obtenidos por tell() a seek(cookie).

No sumes números arbitrariamente a posiciones de texto. Para acceso por bytes, trabaja en la capa binaria.

Streams no bloqueantes

Un raw stream no bloqueante puede devolver None cuando no hay datos y escribir parcialmente. Las capas buffered y text pueden lanzar BlockingIOError.

El artículo anterior, select en Python, explica cómo esperar disponibilidad antes de repetir.

detach()

detach() separa y devuelve la capa subyacente. El wrapper exterior queda inutilizable.

buffer_binario = texto.detach()

Úsalo solo con transferencia de ownership clara. StringIO y BytesIO no tienen una capa inferior separable.

Descriptores existentes y closefd

Cuando open() o FileIO envuelve un descriptor entero, closefd=False evita que cerrar el stream cierre ese descriptor. Se necesitan reglas explícitas para evitar leaks o double close.

opener personalizado

El argumento opener controla cómo se crea el descriptor y puede permitir apertura relativa a un directorio.

import os

raiz_fd = os.open("datos", os.O_RDONLY)
try:
    def opener(path, flags):
        return os.open(path, flags, dir_fd=raiz_fd)

    with open("archivo.txt", "r", encoding="utf-8", opener=opener) as archivo:
        print(archivo.read())
finally:
    os.close(raiz_fd)

Valida nombres y path traversal. Un opener no crea automáticamente una política segura.

Compresión y file-like objects

Muchos módulos aceptan objetos file-like, permitiendo componer capas.

import gzip
import io

origen = io.BytesIO(datos_comprimidos)
with gzip.GzipFile(fileobj=origen, mode="rb") as archivo:
    contenido = archivo.read(1_000_000)

La descompresión en memoria sigue necesitando límites. Consulta gzip en Python.

Protocolos Reader y Writer en Python 3.14

Python 3.14 añade io.Reader[T] y io.Writer[T] para tipar funciones que solo requieren read() o write().

from io import Reader, Writer


def copiar_texto(origen: Reader[str], destino: Writer[str]) -> None:
    while bloque := origen.read(8192):
        destino.write(bloque)

El tipado estructural admite archivos, StringIO e implementaciones compatibles.

Thread safety y reentrancia

FileIO sigue las garantías de las syscalls. Los objetos binarios buffered protegen estructuras con locks y pueden ser llamados por varios threads. TextIOWrapper no es thread-safe.

Los objetos buffered no son reentrantes. Hacer I/O sobre el mismo stream desde un signal handler puede lanzar RuntimeError. El artículo de signal en Python recomienda handlers mínimos.

Rendimiento

Buffered I/O ofrece rendimiento predecible y suele ser preferible al raw I/O. Text I/O añade coste de codec. Para archivos grandes, procesa bloques y evita read() sin límite.

StringIO y BytesIO son eficientes para tamaños moderados, pero almacenan todo en RAM. Usa archivos temporales o streaming para volúmenes grandes.

Pruebas recomendadas

Prueba texto y bytes, Unicode, encoding inválido, newlines, archivo vacío, lecturas y escrituras parciales, non-blocking, seek/tell, cierre repetido, detach, views activas de BytesIO, StringIO, ownership de descriptor y límites de memoria.

Errores comunes

Los fallos frecuentes son omitir encoding, mezclar str y bytes, usar errors="ignore", asumir escritura completa en raw I/O, leer archivos enormes de una vez, confundir flush con persistencia, calcular offsets de texto como bytes y cerrar un descriptor propiedad de otra capa.

Conclusión

io proporciona los contratos centrales de streams de Python. Texto, bytes, acceso bruto, buffering y memoria son capas distintas que pueden combinarse explícitamente.

Especifica encoding, prefiere buffering, valida retornos de raw streams, usa context managers y limita recursos. Consulta la documentación oficial de io y la documentación oficial de open().

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Vibrant green tree python elegantly coiled on branch, showcasing its natural beauty.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    select en Python: monitorea varios I/O

    Aprende select en Python para monitorear sockets y pipes, tratar I/O parcial, backpressure, poll, epoll, señales y diferencias de plataforma.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    signal en Python: cierre correcto

    Aprende signal en Python para manejar SIGTERM y SIGINT, detener servicios, usar timers, wakeup FD y evitar deadlocks en handlers.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Creative concept showing the word 'error' with cut out letters on a table with scissors and paper.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    errno en Python: errores del sistema

    Aprende errno en Python para interpretar códigos del sistema, tratar OSError, archivos, red, retries y llamadas nativas de forma portable.

    Ler mais

    Tempo de leitura: 4 minutos
    24/08/2026
    African American man using a laptop in a well-organized library setting with colorful bookshelves.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ctypes en Python: usa bibliotecas C

    Aprende ctypes en Python para cargar bibliotecas C, definir tipos y punteros, gestionar memoria, callbacks, ABI y errores nativos.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    expat en Python: parser XML de bajo nivel

    Aprende expat en Python para parsing XML de bajo nivel, handlers, namespaces, diagnósticos y protección contra amplificación y DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude en Python: XInclude seguro

    Aprende ElementInclude en Python para usar XInclude con loaders seguros, base URL, profundidad máxima y bloqueo de rutas externas.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026