quopri en Python: quoted-printable

Publicado el: 08/08/2026
Tempo de leitura: 6 minutos
Mensaje digital que representa codificación quoted-printable con quopri en Python

Los mensajes de correo y algunos protocolos orientados a texto necesitan transportar bytes por canales que no siempre conservan todos los valores. Quoted-printable resuelve este problema manteniendo legible el ASCII común y representando bytes especiales mediante secuencias que empiezan con =. quopri en Python implementa esta codificación de transporte para cadenas de bytes y streams binarios.

Quoted-printable funciona mejor cuando el contenido es principalmente texto común y contiene pocos bytes no imprimibles o fuera de ASCII. Cuando los datos son binarios o tienen muchos valores especiales, Base64 suele ser más compacto y predecible. Esta guía explica las APIs de strings y archivos, el modo de cabecera, los charsets, los saltos suaves, la validación y la integración segura.

El tema complementa nuestras guías sobre mimetypes, fileinput, tempfile, filecmp y shlex.

Qué hace quoted-printable

El formato fue diseñado para contenido mayoritariamente legible en ASCII con una pequeña cantidad de bytes especiales. Letras, números y muchos signos permanecen visibles. Otros bytes se convierten en un signo igual seguido de dos dígitos hexadecimales.

Café  →  Caf=C3=A9

El ejemplo supone que el texto fue convertido primero a bytes UTF-8. Quoted-printable no entiende caracteres Unicode directamente; transforma bytes.

Codificar bytes con encodestring

import quopri

bruto = "Hola, café".encode("utf-8")
codificado = quopri.encodestring(bruto)
print(codificado)

La función recibe bytes y devuelve bytes. Si partes de un string de Python, elige explícitamente el charset. UTF-8 es habitual, aunque un sistema heredado puede declarar otro.

Decodificar con decodestring

datos = b"Hola, caf=C3=A9"
decodificado = quopri.decodestring(datos)
texto = decodificado.decode("utf-8")
print(texto)

Eliminar la codificación de transporte y convertir bytes a texto son pasos diferentes. Primero decodifica quoted-printable y después aplica el charset indicado por el protocolo.

Charset y transferencia no son lo mismo

UTF-8 describe cómo los caracteres se convierten en bytes. Quoted-printable explica cómo esos bytes viajan por un canal textual. Una parte MIME puede usar simultáneamente charset=utf-8 y Content-Transfer-Encoding: quoted-printable.

Omitir una capa produce texto corrupto o secuencias hexadecimales visibles. Documenta qué capa recibe y devuelve cada función.

El parámetro quotetabs

encodestring() acepta quotetabs. Cuando es verdadero, también codifica espacios y tabs internos.

datos = b"campo  con\ttabulador"
print(quopri.encodestring(datos, quotetabs=True))

Los espacios y tabs al final de una línea siempre se codifican porque muchos sistemas eliminan whitespace final. La regla evita cambios silenciosos.

Saltos de línea suaves

Las líneas quoted-printable tienen un límite de longitud. El encoder puede colocar un signo igual al final de una línea física para indicar que el salto solo pertenece al transporte.

contenido lógico muy largo=
continúa aquí

El decoder elimina el salto suave y reconstruye la secuencia original. No proceses cada línea física de forma independiente sin conservar esta regla.

Codificar archivos y streams

Para entradas grandes utiliza quopri.encode() con objetos binarios.

import quopri

with open("mensaje.txt", "rb") as origen:
    with open("mensaje.qp", "wb") as destino:
        quopri.encode(origen, destino, quotetabs=False)

El procesamiento por stream evita cargar todo el archivo en memoria. Ambos objetos deben abrirse en modo binario.

Decodificar archivos

with open("mensaje.qp", "rb") as origen:
    with open("mensaje.txt", "wb") as destino:
        quopri.decode(origen, destino)

Un fallo de I/O puede dejar un archivo parcialmente escrito. Cuando la atomicidad importa, escribe en un temporal, valida el resultado y reemplaza el destino solamente después del éxito.

Modo de cabecera

La opción header=True aplica las convenciones de palabras Q-encoded en cabeceras MIME. Durante la decodificación, underscore se interpreta como espacio.

valor = b"Informe_mensual=C3=A9"
print(quopri.decodestring(valor, header=True))

No actives este modo para el cuerpo común de un mensaje. Un underscore literal del cuerpo debe conservarse.

Usar email para mensajes completos

quopri es una herramienta de bajo nivel. Para mensajes completos, prefiere el paquete email, que entiende cabeceras, multiparts, charsets, adjuntos, políticas y plegado de líneas.

Construir límites MIME y cabeceras manualmente aumenta el riesgo de inyección, saltos inválidos y problemas de compatibilidad. Usa quopri directamente cuando controles una capa concreta o debas interoperar con un protocolo simple.

Cuándo elegir quoted-printable

  • Texto principalmente ASCII con pocos caracteres acentuados.
  • Contenido que debe permanecer parcialmente legible.
  • Sistemas MIME heredados.
  • Protocolos restringidos a líneas textuales.
  • Archivos de diagnóstico destinados a inspección humana.

Para imágenes, PDFs, archivos comprimidos y contenido denso en bytes no ASCII, Base64 normalmente genera una representación más uniforme.

Comparación con Base64

Base64 aumenta el tamaño de manera relativamente constante y oculta toda la estructura visual. Quoted-printable conserva la mayor parte del ASCII, pero cada byte especial puede convertirse en tres caracteres. Un documento en un idioma con muchos caracteres no ASCII puede quedar más grande que en Base64.

Mide muestras reales cuando importen tamaño, legibilidad, compatibilidad o almacenamiento.

Entradas malformadas

Los datos externos pueden incluir signos igual incompletos, pares hexadecimales inválidos, líneas enormes o saltos extraños. El decoder es tolerante en varias situaciones, por lo que una devolución exitosa no demuestra que la fuente cumpla el estándar.

Aplica límites, registra anomalías cuando corresponda y valida el formato de nivel superior después de decodificar.

Seguridad y límites

Decodificar no ejecuta código, pero el resultado puede ser HTML, script, comando, ruta o adjunto malicioso. Trata los bytes según su contexto final. No renderices HTML de correo sin sanitización ni pases valores a shells o rutas directamente.

Limita el tamaño de entrada y salida. Un cuerpo enorme puede consumir memoria o disco aunque la transformación sea sencilla.

Normalización de líneas

El correo de Internet usa CRLF; los archivos locales pueden usar LF o CRLF. Evita normalizar antes de la decodificación porque los saltos suaves dependen de la representación original.

Después de recuperar el cuerpo, aplica la política de saltos correspondiente al contenido.

Helpers con charset explícito

def codificar_texto(texto: str, charset: str = "utf-8") -> bytes:
    bruto = texto.encode(charset, errors="strict")
    return quopri.encodestring(bruto, quotetabs=False)


def decodificar_texto(datos: bytes, charset: str = "utf-8") -> str:
    bruto = quopri.decodestring(datos)
    return bruto.decode(charset, errors="strict")

El modo estricto evita reemplazos silenciosos. Captura UnicodeError para informar que el charset declarado no coincide.

Streams en memoria

from io import BytesIO

origen = BytesIO(b"texto con acento: \xc3\xa7")
destino = BytesIO()
quopri.encode(origen, destino, quotetabs=False)
resultado = destino.getvalue()

BytesIO es útil en pruebas y en APIs que entregan objetos similares a archivos.

Prueba de ida y vuelta

def test_round_trip():
    original = "café y Python".encode("utf-8")
    codificado = quopri.encodestring(original)
    restaurado = quopri.decodestring(codificado)
    assert restaurado == original

Añade casos con espacios finales, tabs, líneas largas, bytes nulos, underscores de cabecera, entrada vacía y todos los charsets soportados.

Salida temporal y reemplazo atómico

Las herramientas de producción deben escribir en un archivo temporal del mismo sistema de archivos. Después de validar tamaño y contenido, reemplaza atómicamente el destino. Así un fallo no deja datos corruptos bajo el nombre final.

Diseñar una interfaz de comandos

Para automatizaciones duraderas crea una interfaz con argparse. Puede elegir codificación o decodificación, modo de cabecera, tamaño máximo, origen, destino y charset.

Errores frecuentes

  • Pasar str donde se esperan bytes.
  • Eliminar quoted-printable y olvidar el charset.
  • Usar header=True sobre un cuerpo.
  • Confundir underscore literal con espacio de cabecera.
  • Elegir quoted-printable para grandes archivos binarios.
  • Procesar líneas por separado y perder soft breaks.
  • Suponer que la salida es segura.
  • Escribir directamente sobre el destino definitivo.

Buenas prácticas

  • Mantén las transformaciones en bytes hasta conocer el charset.
  • Usa email para mensajes completos.
  • Prefiere streams para entradas grandes.
  • Limita tamaños codificados y decodificados.
  • Valida el formato final por separado.
  • Activa header únicamente para cabeceras Q-encoded.
  • Prueba whitespace final y líneas largas.
  • Compara con Base64 usando datos representativos.

Conclusión

quopri en Python implementa quoted-printable para bytes y streams. Es especialmente útil para contenido MIME mayoritariamente textual que debe atravesar canales restringidos sin perder por completo la legibilidad.

La transformación es sencilla, pero la integración correcta exige separar capas: charset y transferencia son conceptos distintos, las cabeceras usan reglas especiales y la salida necesita validación. Consulta la documentación oficial de quopri y la RFC 2045 para los requisitos completos.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Icono de archivo digital que representa tipos MIME con mimetypes en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    mimetypes en Python: tipos MIME

    Aprende mimetypes en Python para identificar tipos MIME, extensiones y encodings de forma segura en cargas, descargas, correo y APIs

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Búsqueda binaria y listas ordenadas con bisect en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    bisect en Python: listas ordenadas

    Aprende bisect en Python para búsqueda binaria, inserción ordenada, duplicados, rangos y diseño seguro de listas.

    Ler mais

    Tempo de leitura: 5 minutos
    08/08/2026
    Código y archivos empaquetados con importlib.resources en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    importlib.resources en Python: guía práctica

    Aprende importlib.resources en Python para acceder a archivos empaquetados con seguridad en wheels y aplicaciones instaladas.

    Ler mais

    Tempo de leitura: 5 minutos
    07/08/2026
    Teclado y flujo de datos que representa el procesamiento de varios archivos con fileinput en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fileinput en Python: lee varios archivos

    Aprende fileinput en Python para leer varios archivos o stdin, rastrear líneas, abrir archivos comprimidos y reescribir contenido con backups.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Editor de código con líneas numeradas que representa el módulo linecache en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    linecache en Python: lee líneas por número

    Aprende linecache en Python para leer líneas por número, administrar la caché, actualizar archivos modificados e integrar traceback y loaders.

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Datos binarios que representan serialización interna con marshal en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    marshal en Python: serialización interna

    Aprende marshal en Python para serializar tipos internos, controlar versiones y bloquear objetos de código cuando no sean necesarios.

    Ler mais

    Tempo de leitura: 6 minutos
    06/08/2026