codecs en Python: domina encodings

Publicado el: 18/08/2026
Tempo de leitura: 6 minutos
Close-up view of a computer screen displaying code in a software development environment.

El módulo codecs en Python ofrece acceso al registro de codificaciones, funciones genéricas para codificar y decodificar, procesadores incrementales, clases de lectura y escritura de streams, constantes BOM y mecanismos para registrar codecs y manejadores de errores personalizados.

La mayoría de los programas no necesita importar codecs para abrir un archivo UTF-8. La función integrada open() y el módulo io son las interfaces recomendadas. El módulo resulta importante cuando una aplicación debe inspeccionar el registro, procesar secuencias parciales, transcodificar streams, usar transformaciones no textuales o implementar una codificación.

Texto y bytes son tipos diferentes

Una cadena de Python contiene puntos de código Unicode. Los archivos y protocolos transportan bytes, por lo que el texto debe codificarse y los bytes deben decodificarse:

texto = "Hola, Python"
datos = texto.encode("utf-8")
restaurado = datos.decode("utf-8")

assert restaurado == texto

El nombre de la codificación forma parte del protocolo. Decodificar bytes UTF-8 como Latin-1 puede producir texto aparentemente válido pero corrupto. No intentes adivinar silenciosamente cuando el productor debería declarar el encoding.

codecs.encode y codecs.decode

import codecs

bytes_utf8 = codecs.encode("precio €", "utf-8", errors="strict")
texto = codecs.decode(bytes_utf8, "utf-8", errors="strict")

print(bytes_utf8)
print(texto)

Estas funciones utilizan el registro y también pueden invocar transformaciones que no están disponibles en str.encode() o bytes.decode(). Para codificaciones de texto comunes, los métodos de los objetos son más directos.

Consulta el registro

import codecs

info = codecs.lookup("utf-8")
print(info.name)
print(info.encode)
print(info.decode)
print(info.incrementalencoder)
print(info.incrementaldecoder)

lookup() resuelve aliases y devuelve un CodecInfo. Un nombre desconocido lanza LookupError. Valida el encoding configurable durante el arranque, no al procesar el primer archivo.

Aliases y nombres normalizados

Nombres como utf-8, UTF_8 y utf8 normalmente apuntan al mismo codec. Espacios y guiones se normalizan. Aun así, utiliza nombres consistentes y documentados. CPython optimiza algunos aliases comunes, mientras variantes inusuales pueden perder esas optimizaciones.

Usa open para archivos de texto

from pathlib import Path

ruta = Path("informe.txt")

with ruta.open("w", encoding="utf-8", newline="\n") as archivo:
    archivo.write("línea 1\nlínea 2\n")

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

codecs.open() está deprecado desde Python 3.14 y ha sido reemplazado por open(). La API moderna integra buffering, manejo de newlines y la infraestructura de io.

Migra codecs.open

# Antiguo
import codecs
archivo = codecs.open("datos.txt", "r", encoding="utf-8")

# Actual
archivo = open("datos.txt", "r", encoding="utf-8")

Revisa las diferencias de newline durante la migración. Cuando codecs.open() recibe un encoding, el archivo subyacente se abre en binario y no realiza conversión automática de \n. Prueba archivos de Windows y Unix.

Manejo de errores

La política predeterminada strict lanza UnicodeEncodeError o UnicodeDecodeError. Es la opción más segura cuando no se acepta pérdida.

datos = b"nombre: Jos\xe9"

try:
    texto = datos.decode("utf-8", errors="strict")
except UnicodeDecodeError as error:
    print(error.start, error.end, error.reason)

Los handlers principales son:

  • strict: lanza una excepción.
  • replace: inserta un carácter de reemplazo o interrogación.
  • ignore: descarta datos inválidos.
  • backslashreplace: conserva información como escapes.
  • surrogateescape: permite round trip de bytes del sistema.
  • xmlcharrefreplace: genera referencias numéricas.
  • namereplace: utiliza nombres Unicode en escapes.

Evita errors ignore

errors="ignore" puede eliminar letras, separadores, números o caracteres relevantes para seguridad sin aviso. Convierte datos corruptos en datos aparentemente válidos. Úsalo solo cuando la pérdida sea intencional, medible y no cambie significado.

Conserva bytes desconocidos

datos = b"archivo_\xff.txt"
texto = datos.decode("utf-8", errors="surrogateescape")
restaurado = texto.encode("utf-8", errors="surrogateescape")
assert restaurado == datos

La cadena intermedia contiene puntos surrogate. Su finalidad es devolver los bytes a la misma interfaz del sistema; no la envíes sin control a JSON, bases de datos o interfaces.

Registra un handler de errores

import codecs
import logging

logger = logging.getLogger(__name__)

def registrar_y_reemplazar(error):
    logger.warning("fallo de encoding entre %d y %d", error.start, error.end)
    return ("?", error.end)

codecs.register_error("app_registrar_reemplazar", registrar_y_reemplazar)
resultado = "precio: €".encode("ascii", errors="app_registrar_reemplazar")

El handler debe avanzar la posición para no crear un bucle infinito. Evita registrar contenido sensible y usa nombres con prefijo del proyecto.

Decodificación incremental

Un carácter UTF-8 puede quedar dividido entre bloques. Decodificar cada bloque de forma independiente puede fallar. Un decoder incremental guarda secuencias incompletas:

import codecs

decoder = codecs.getincrementaldecoder("utf-8")(errors="strict")
partes = []

for bloque in recibir_bloques():
    partes.append(decoder.decode(bloque, final=False))

partes.append(decoder.decode(b"", final=True))
texto = "".join(partes)

La llamada final procesa el estado pendiente y lanza error si el stream termina con una secuencia incompleta.

Codificación incremental

import codecs

encoder = codecs.getincrementalencoder("utf-16")()
salida = bytearray()

for fragmento in generar_texto():
    salida.extend(encoder.encode(fragmento, final=False))

salida.extend(encoder.encode("", final=True))

Las codificaciones con estado pueden emitir marcadores o conservar información entre llamadas. No crees un encoder nuevo por fragmento.

iterencode e iterdecode

import codecs

bloques_texto = ["Hola ", "mundo", "!\n"]
for bloque in codecs.iterencode(bloques_texto, "utf-8"):
    enviar(bloque)

bloques_bytes = [b"Ol\xc3", b"\xa1 mundo"]
texto = "".join(codecs.iterdecode(bloques_bytes, "utf-8"))

iterencode() requiere cadenas; iterdecode() requiere bytes. Algunas transformaciones binarias o texto-a-texto no son compatibles.

Archivos de texto grandes

El objeto devuelto por open() ya usa un decoder incremental. Iterar por líneas evita cargar todo. Las técnicas de la guía para leer archivos gigantes con Python siguen siendo válidas: bloques, límites, streaming y salida incremental.

BOM en UTF-16 y UTF-32

UTF-16 y UTF-32 pueden usar un Byte Order Mark para identificar endianness. El módulo expone constantes como BOM_UTF16_LE, BOM_UTF16_BE, BOM_UTF32_LE y BOM_UTF32_BE.

import codecs

datos = codecs.BOM_UTF16_LE + "texto".encode("utf-16-le")
print(datos.startswith(codecs.BOM_UTF16_LE))

El codec utf-16 interpreta o genera un BOM. Las variantes explícitas utf-16-le y utf-16-be definen el orden y no deben depender de una marca implícita.

UTF-8 con firma

UTF-8 no necesita orden de bytes, pero algunos programas escriben EF BB BF como firma. utf-8-sig elimina el prefijo al leer y lo añade al escribir:

from pathlib import Path

texto = Path("planilla.csv").read_text(encoding="utf-8-sig")

Úsalo para interoperar con software que espera BOM. En protocolos nuevos, prefiere UTF-8 normal y declara la codificación externamente.

Detectar encoding es incierto

No existe un algoritmo perfecto para identificar la codificación de bytes arbitrarios. Muchas secuencias son válidas en varias páginas de código. Prefiere metadatos, cabeceras, contratos o configuración validada. Las heurísticas deben informar confianza y permitir revisión.

Transcodifica archivos

from pathlib import Path

origen = Path("legado.txt")
destino = Path("nuevo.txt")

with origen.open("r", encoding="latin-1", errors="strict") as entrada:
    with destino.open("w", encoding="utf-8", newline="") as salida:
        for linea in entrada:
            salida.write(linea)

Transcodificar significa decodificar y luego codificar. No reemplaces bytes directamente. Escribe en un archivo temporal y renómbralo tras el éxito.

Codecs personalizados

codecs.register() añade una función de búsqueda que recibe un nombre normalizado y devuelve CodecInfo o None. unregister(), añadido en Python 3.10, la elimina y limpia la caché.

Un codec completo puede definir funciones stateless, clases incrementales y factories de stream. Impleméntalo solo para un formato real compartido; una función normal es más sencilla para una transformación privada.

Transformaciones binarias en el registro

import codecs

codificado = codecs.encode(b"datos", "base64_codec")
restaurado = codecs.decode(codificado, "base64_codec")

El registro incluye transformaciones bytes-a-bytes como Base64, hex, bz2 y zlib. Para Base64 de aplicación, base64 en Python es más claro y permite validación estricta. Para compresión, usa los módulos específicos.

Encoding no es un layout binario

Una codificación de texto convierte caracteres en bytes. No define campos, números ni cabeceras. Usa struct en Python para registros binarios versionados con endianness y longitudes explícitas.

Encodings configurables

Cuando el nombre viene de configuración, valídalo con codecs.lookup() al iniciar. configparser en Python puede cargarlo, pero utiliza una allowlist cuando solo se admiten codificaciones de texto, porque el registro también contiene transformaciones binarias.

Seguridad y límites

  • Limita bytes de origen y texto resultante.
  • Usa strict para datos críticos.
  • Evita detección automática sin metadatos.
  • No expongas surrogates en formatos externos.
  • Finaliza los decoders incrementales.
  • No registres contenido sensible.
  • Usa allowlist de encodings.
  • Limita entradas IDNA y Punycode no confiables.

Pruebas recomendadas

Prueba ASCII, acentos, emoji, caracteres no representables, secuencias UTF-8 divididas, último bloque incompleto, BOM correcto e invertido, firma UTF-8, archivos vacíos, newlines de Windows y Unix, handlers, aliases, round trip y bytes esperados.

Buenas prácticas

  • Usa UTF-8 en formatos nuevos.
  • Declara el encoding en el protocolo.
  • Usa open() en lugar de codecs.open().
  • Prefiere strict y maneja fallos.
  • Usa decoder incremental para chunks manuales.
  • Valida nombres del registro.
  • Mantén texto como str y transporte como bytes.
  • Transcodifica en streaming.

Conclusión

codecs en Python es la infraestructura que conecta nombres de encoding, funciones, streams, políticas de error y procesamiento incremental. Es especialmente útil en bibliotecas, protocolos e interoperabilidad; los archivos de texto normales deben continuar usando open().

Consulta la documentación oficial de codecs y el Unicode Standard. La codificación correcta debe proceder de un contrato, no de prueba y error.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    A person reads 'Python for Unix and Linux System Administration' indoors.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    base64 en Python: codifica datos

    Aprende base64 en Python para codificar bytes, usar Base64 URL-safe, validar padding, aplicar límites y diferenciar encoding de cifrado.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Detailed image of a Burmese Python being held. Captured in Toluca, Mexico.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    urllib.parse en Python: maneja URLs

    Aprende urllib.parse en Python para dividir URLs, crear queries, codificar componentes y evitar riesgos con urljoin, redirects, logs y SSRF.

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    Close-up view of a computer screen displaying code in a software development environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    ipaddress en Python: redes IPv4 e IPv6

    Aprende ipaddress en Python para validar IPv4 e IPv6, calcular redes CIDR, dividir subredes y crear políticas de acceso más

    Ler mais

    Tempo de leitura: 5 minutos
    18/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    queue en Python: coordina hilos

    Aprende queue en Python para coordinar hilos con FIFO, prioridad, backpressure, tracking, reintentos y shutdown seguro.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Chic portrait of a woman wearing trendy sunglasses reflecting numbers, captured in a modern setting.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    struct en Python: datos binarios

    Aprende struct en Python para empaquetar datos binarios, controlar endianness, reutilizar buffers y validar protocolos externos.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile en Python: crea TAR seguro

    Aprende tarfile en Python para crear TAR comprimido, inspeccionar miembros y extraer con filtros, límites y protección de rutas.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026