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 == textoEl 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 == datosLa 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
strictpara 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 decodecs.open(). - Prefiere
stricty maneja fallos. - Usa decoder incremental para chunks manuales.
- Valida nombres del registro.
- Mantén texto como
stry 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.







