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 aquiO 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 == originalInclua 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
strquando a API esperabytes. - Decodificar quoted-printable e esquecer o charset.
- Usar
header=Trueno 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
emailpara 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.







