quopri no Python: quoted-printable

Publicado em: 08/08/2026
Tempo de leitura: 7 minutos
Mensagem digital representando codificação quoted-printable com quopri no Python

Mensagens de e-mail e protocolos antigos precisam transportar texto por canais que nem sempre preservam todos os bytes. O formato quoted-printable resolve esse problema mantendo caracteres ASCII legíveis e representando bytes especiais com sequências iniciadas por =. O módulo quopri no Python implementa essa codificação e sua decodificação para strings de bytes e arquivos binários.

Quoted-printable funciona melhor quando o conteúdo é majoritariamente texto comum e possui poucos caracteres fora do conjunto imprimível. Quando os dados são binários ou contêm muitos bytes especiais, Base64 costuma ser menor e mais previsível. Neste guia você aprenderá a escolher o formato, trabalhar com streams, tratar cabeçalhos, preservar encodings e evitar erros de segurança.

O tema complementa nossos guias sobre mimetypes, fileinput, tempfile, filecmp e shlex.

O que é quoted-printable

O formato foi criado para transportar conteúdo que é quase todo legível em ASCII, mas contém alguns bytes especiais. Letras, números e vários sinais permanecem visíveis. Outros bytes são escritos como um sinal de igual seguido por dois dígitos hexadecimais.

Olá, mundo!  →  Ol=C3=A1, mundo!

O exemplo pressupõe que o texto foi convertido para bytes UTF-8 antes da codificação. Quoted-printable não conhece Unicode: ele apenas transforma bytes.

Codificar bytes com encodestring

import quopri

texto = "Olá, Python!".encode("utf-8")
codificado = quopri.encodestring(texto)
print(codificado)

A função recebe bytes e devolve bytes. Se você possui uma string, escolha explicitamente o encoding antes de chamar a função. UTF-8 é uma opção comum, mas sistemas legados podem exigir outro formato.

Decodificar com decodestring

dados = b"Ol=C3=A1, Python!"
decodificado = quopri.decodestring(dados)
texto = decodificado.decode("utf-8")
print(texto)

A decodificação quoted-printable e a conversão de bytes para texto são etapas separadas. Primeiro remova a codificação de transporte; depois aplique o charset informado pelo protocolo ou pela aplicação.

Não confunda charset e codificação de transporte

UTF-8 descreve como caracteres viram bytes. Quoted-printable descreve como esses bytes são transportados por um canal textual. Uma mensagem pode usar charset=utf-8 e Content-Transfer-Encoding: quoted-printable ao mesmo tempo.

Decodificar apenas uma camada produz texto corrompido ou sequências hexadecimais ainda visíveis. Em integrações, registre claramente qual camada cada função recebe e devolve.

O parâmetro quotetabs

encodestring() aceita quotetabs. Quando verdadeiro, espaços e tabs internos também são codificados.

dados = b"campo  com\ttabulacao"
print(quopri.encodestring(dados, quotetabs=True))

Espaços e tabs no fim de linhas sempre precisam ser codificados, porque muitos sistemas removem whitespace final. Essa regra protege o conteúdo contra alterações silenciosas.

Quebras de linha suaves

Linhas quoted-printable possuem limite de comprimento. O encoder pode inserir um sinal de igual no fim da linha para indicar que a quebra é apenas de transporte.

parte muito longa=
continua aqui

O decoder remove a quebra suave e reconstrói a sequência original. Não processe cada linha isoladamente sem considerar essa regra.

Codificar arquivos e streams

Para grandes volumes, use quopri.encode() com objetos binários de entrada e saída.

import quopri

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

O processamento por stream evita manter todo o conteúdo em memória. Abra ambos os arquivos em modo binário, porque o módulo opera sobre bytes.

Decodificar arquivos

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

Erros de abertura e leitura podem gerar exceções de I/O. Grave em um arquivo temporário e substitua o destino somente após sucesso quando a operação precisa ser atômica.

Modo de cabeçalho

O parâmetro header=True aplica regras usadas em cabeçalhos MIME codificados. Na decodificação, underscore é interpretado como espaço.

valor = b"Relat=C3=B3rio_mensal"
print(quopri.decodestring(valor, header=True))

Não habilite esse modo para corpos de mensagens comuns. Um underscore literal no corpo deve permanecer underscore, enquanto em um cabeçalho Q-encoded ele pode representar espaço.

Use a biblioteca email para mensagens completas

quopri é uma ferramenta de baixo nível. Para analisar ou produzir mensagens de e-mail completas, prefira o pacote email, que entende cabeçalhos, multiparts, charsets, anexos e políticas.

Manipular manualmente limites MIME e cabeçalhos facilita injeções, linhas inválidas e mensagens incompatíveis. Use quopri diretamente quando você controla uma parte específica do fluxo ou precisa interoperar com um protocolo simples.

Quando escolher quoted-printable

O formato é adequado para:

  • texto com poucas letras acentuadas;
  • conteúdo que precisa permanecer parcialmente legível;
  • mensagens MIME legadas;
  • protocolos que exigem linhas ASCII;
  • diagnósticos em que a legibilidade ajuda.

Para imagens, PDFs, arquivos compactados e conteúdo com muitos bytes não ASCII, Base64 normalmente produz menos expansão.

Comparação com Base64

Base64 aumenta o tamanho de maneira relativamente constante e esconde toda a estrutura visual. Quoted-printable preserva a maior parte do ASCII, mas cada byte especial pode virar três caracteres. Um texto em idioma com muitos caracteres não ASCII pode ficar maior em quoted-printable do que em Base64.

Meça com amostras reais quando tamanho, compatibilidade e legibilidade importam.

Dados malformados

Entradas externas podem conter sinais de igual incompletos, hexadecimais inválidos, linhas gigantes ou quebras inesperadas. O decoder é tolerante em vários casos, portanto não use a ausência de exceção como prova de validade.

Defina limites de tamanho antes de decodificar, registre anomalias e valide o formato de nível superior depois da transformação.

Segurança e expansão

A decodificação não executa código, mas o resultado pode ser HTML, script, comando, arquivo ou mensagem maliciosa. Trate os bytes decodificados conforme seu contexto. Não renderize HTML de e-mail sem sanitização e não use o conteúdo como caminho, comando ou consulta.

Limite o tamanho do corpo recebido e do resultado. Mesmo uma transformação simples pode ser usada para consumir memória quando a aplicação aceita entradas enormes.

Normalização de linhas

Protocolos de e-mail usam CRLF como separador de linha. Arquivos locais podem usar LF ou CRLF. Evite converter linhas antes de terminar a decodificação, pois quebras suaves dependem da estrutura original.

Após obter os bytes do corpo, aplique a política de normalização apropriada ao conteúdo, não ao envelope de transporte.

Exemplo com 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(dados: bytes, charset: str = "utf-8") -> str:
    bruto = quopri.decodestring(dados)
    return bruto.decode(charset, errors="strict")

Usar errors="strict" evita substituir caracteres silenciosamente. A aplicação pode capturar UnicodeError e informar que o charset declarado não corresponde aos bytes.

Processamento em memória com BytesIO

from io import BytesIO

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

BytesIO facilita testes e integração com APIs que produzem streams sem criar arquivos temporários.

Teste de ida e volta

def test_round_trip():
    original = "ação, café e Python".encode("utf-8")
    codificado = quopri.encodestring(original)
    restaurado = quopri.decodestring(codificado)
    assert restaurado == original

Inclua espaços no fim da linha, tabs, linhas longas, bytes nulos, underscores em cabeçalhos, diferentes charsets e entradas vazias.

Integração com arquivos temporários

Ao converter arquivos em produção, escreva a saída em um arquivo temporário no mesmo sistema de arquivos. Após validar tamanho e conteúdo, faça uma substituição atômica. Isso evita deixar um destino parcialmente escrito quando o processo falha.

Interface de linha de comando

O módulo pode ser executado como script em algumas instalações, mas para automações duradouras é melhor criar uma interface explícita com argparse. Ela pode impor limites, escolher modo de cabeçalho e controlar arquivos de entrada e saída.

Erros frequentes

  • Passar str quando a API espera bytes.
  • Decodificar quoted-printable e esquecer o charset.
  • Usar header=True no corpo da mensagem.
  • Confundir underscore literal com espaço de cabeçalho.
  • Escolher quoted-printable para grandes arquivos binários.
  • Processar linhas separadamente e perder soft breaks.
  • Presumir que saída decodificada é segura.
  • Reescrever o destino diretamente sem arquivo temporário.

Boas práticas

  • Mantenha todas as etapas em bytes até conhecer o charset.
  • Use o pacote email para mensagens completas.
  • Prefira streams para arquivos grandes.
  • Limite tamanhos antes e depois da decodificação.
  • Valide o conteúdo conforme o formato final.
  • Use modo de cabeçalho apenas em Q-encoded words.
  • Teste espaços finais e linhas longas.
  • Compare com Base64 usando dados reais.

Conclusão

O módulo quopri no Python oferece uma implementação direta de quoted-printable para bytes e streams. Ele é especialmente útil em conteúdo textual que precisa atravessar canais MIME mantendo boa parte do ASCII legível.

A implementação é simples, mas o contexto exige atenção: charset e encoding de transporte são camadas diferentes, cabeçalhos usam regras próprias e a saída ainda precisa de validação. Consulte a documentação oficial de quopri e a especificação MIME da RFC 2045 para interoperabilidade completa.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Ícone de arquivo digital representando tipos MIME com mimetypes no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    mimetypes no Python: tipos MIME

    Aprenda mimetypes no Python para identificar tipos MIME, extensões e encodings com segurança em uploads, downloads e APIs web.

    Ler mais

    Tempo de leitura: 7 minutos
    08/08/2026
    Busca binária e listas ordenadas com bisect no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    bisect no Python: buscas em listas ordenadas

    Aprenda bisect no Python para buscar posições, inserir valores e trabalhar com duplicatas e faixas em listas ordenadas.

    Ler mais

    Tempo de leitura: 5 minutos
    08/08/2026
    Código e arquivos empacotados com importlib.resources no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    importlib.resources no Python: guia prático

    Aprenda importlib.resources no Python para acessar arquivos empacotados com segurança em pacotes, wheels e aplicações instaladas.

    Ler mais

    Tempo de leitura: 6 minutos
    07/08/2026
    Teclado e fluxo de dados representando leitura de vários arquivos com fileinput no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    fileinput no Python: leia vários arquivos

    Aprenda fileinput no Python para ler vários arquivos ou stdin, rastrear linhas, abrir gzip e reescrever conteúdo com backup e

    Ler mais

    Tempo de leitura: 8 minutos
    07/08/2026
    Editor de código com linhas numeradas representando o módulo linecache no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    linecache no Python: leia linhas por número

    Aprenda linecache no Python para ler linhas por número, usar cache, atualizar arquivos modificados e integrar fontes com traceback e

    Ler mais

    Tempo de leitura: 7 minutos
    07/08/2026
    Dados binários representando serialização interna com marshal no Python
    Bibliotecas e Módulos
    Foto de perfil de Leandro Hirt da Academify

    marshal no Python: serialização interna

    Aprenda marshal no Python para serializar tipos internos, controlar versões e bloquear objetos de código com segurança.

    Ler mais

    Tempo de leitura: 7 minutos
    06/08/2026