quopri: codifica y decodifica quoted-printable

Actualizado el: 20/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

    Documento y bandeja de entrada que representan buzones de correo con mailbox en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    mailbox en Python: buzones de correo

    Aprende mailbox en Python para leer, crear y migrar Maildir, mbox y MH con locking, flags, mensajes y manejo seguro

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026
    Editor de texto que representa formato con textwrap en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    textwrap en Python: formatea textos

    Aprende textwrap en Python para dividir, rellenar, acortar, indentar y quitar sangrías con control de ancho, espacios y palabras largas.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Carpeta y lupa que representan filtros de nombres con fnmatch en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    fnmatch en Python: filtra nombres de archivos

    Aprende fnmatch en Python para filtrar nombres de archivos con comodines, controlar mayúsculas, excluir patrones y distinguir glob de regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor con datos binarios que representa arrays numéricos compactos en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    array en Python: números compactos

    Aprende array en Python para almacenar números compactos, usar archivos binarios, byte order, memoryview y buffers seguros.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Círculo cromático que representa conversiones RGB, HSV y HLS con colorsys en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    colorsys en Python: RGB, HSV y HLS

    Aprende colorsys en Python para convertir colores entre RGB, HSV, HLS y YIQ, crear paletas y evitar errores de escala

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Icono de configuración que representa archivos plist con plistlib en Python
    Bibliotecas y Módulos
    Foto de perfil de Leandro Hirt da Academify

    plistlib: lee y escribe archivos plist

    Aprende plistlib en Python para leer y escribir archivos plist XML y binarios, validar datos y manejar fechas, bytes y

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026