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

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026