Как использовать модуль электронной почты Python 3.2 для отправки сообщений Unicode, закодированных в utf-8, с возможностью печати в кавычках?

Я хочу отправлять сообщения электронной почты с произвольными телами Unicode в программе Python 3.2. Но на самом деле эти сообщения будут состоять в основном из 7-битного текста ASCII. Поэтому я хотел бы, чтобы сообщения были закодированы в utf-8 с использованием кавычек-печати. До сих пор я нашел, что это работает, но кажется неправильным:

c = email.charset.Charset('utf-8')
c.body_encoding = email.charset.QP
m = email.message.Message()
m.set_payload("My message with an '\u05d0' in it.".encode('utf-8').decode('iso8859-1'), c)

Это приводит к сообщению электронной почты с точно правильным содержанием:

To: [email protected]
From: [email protected]
Subject: This is a subjective subject.
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

My message with an '=D7=90' in it.

В частности, b'\xd7\x90'.decode('utf-8') приводит к исходному символу Unicode. Таким образом, кодировка quoted-printable правильно отображает utf-8. Я прекрасно понимаю, что это невероятно уродливый хак. Но это работает.

Это Python 3. Ожидается, что текстовые строки всегда будут иметь кодировку Unicode. Мне не нужно было декодировать его в utf-8. А затем превратить его из bytes обратно в str с помощью .decode('iso8859-1') — это ужасный хак, и мне тоже не нужно было этого делать.

Это модуль email просто сломан в отношении кодировок? Я что-то не получаю?

Я попытался просто установить его без набора символов. Это оставляет мне сообщение электронной почты в формате Unicode, и это совсем не так. Я также пытался пропустить шаги encode и decode. Если я оставлю их оба выключенными, он жалуется, что \u05d0 находится вне допустимого диапазона при попытке решить, нужно ли этот символ заключать в кавычки в кодировке для печати в кавычках. Если я ухожу только через encode шаг, он горько жалуется на то, что я прохожу bytes, и хочет str.


person Omnifarious    schedule 22.02.2012    source источник
comment
Если "My message with an '\u05d0' in it." — это юникод, который вам нужен, то вы не можете использовать "My message with an '\u05d0' in it.".encode('utf-8').decode('iso8859-1'), так как это другой юникод. (Вы изменили сообщение.)   -  person unutbu    schedule 23.02.2012
comment
@unutbu: Поздравляю, вы заметили, почему код очень уродлив. Но это работает. Достигается желаемый результат. Смотрите мое обновление.   -  person Omnifarious    schedule 23.02.2012
comment
Для Python 3.6+ см. также сейчас «сообщение электронной почты python3 для отключения base64 и удаления версии mime»> stackoverflow.com/questions/66039715/   -  person tripleee    schedule 04.02.2021


Ответы (2)


Этот пакет электронной почты не путается в том, что есть что (закодированный юникод по сравнению с двоичными данными, закодированными при передаче содержимого), но в документации это не очень ясно, поскольку большая часть документации датируется эпохой, когда «кодирование» имелось в виду кодирование передачи содержимого. Мы работаем над улучшенным API, который упростит поиск (и улучшит документацию).

На самом деле есть способ заставить пакет электронной почты использовать QP для тел utf-8, но он не очень хорошо документирован. Вы делаете это так:

>>> charset.add_charset('utf-8', charset.QP, charset.QP)
>>> m = MIMEText("This is utf-8 text: á", _charset='utf-8')
>>> str(m)
'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nThis is utf-8 text: =E1'
person R. David Murray    schedule 01.03.2012
comment
Благодарю вас! Это прекрасно отвечает на мой вопрос и дает мне возможность делать то, что я хочу, и это не мешает взлому. :-) - person Omnifarious; 03.03.2012
comment
Это прекрасно справляется с вашим характером. Но он не обрабатывает символ =0. На самом деле он кодирует ваш символ не как utf-8, а как iso8859-1. :-/ - person Omnifarious; 03.03.2012
comment
это не удается для 'body …'. Он производит 'body =3DE2=3D80=3DA6' вместо 'body=20=E2=80=A6' в Python 3.3. И тот же код не работает на Python 3.4 с UnicodeEncodeError: 'utf-8' codec can't encode character '\udce2' in position 5: surrogates not allowed - person jfs; 09.03.2014

Бег

import email
import email.charset
import email.message

c = email.charset.Charset('utf-8')
c.body_encoding = email.charset.QP
m = email.message.Message()
m.set_payload("My message with an '\u05d0' in it.", c)
print(m.as_string())

Выдает это сообщение трассировки:

  File "/usr/lib/python3.2/email/quoprimime.py", line 81, in body_check
    return chr(octet) != _QUOPRI_BODY_MAP[octet]
KeyError: 1488

С

In [11]: int('5d0',16)
Out[11]: 1488

ясно, что юникод '\u05d0' является проблемным символом. _QUOPRI_BODY_MAP определяется в quoprimime.py

_QUOPRI_HEADER_MAP = dict((c, '=%02X' % c) for c in range(256))
_QUOPRI_BODY_MAP = _QUOPRI_HEADER_MAP.copy()

Этот словарь содержит только ключи из range(256). Так что я думаю, что вы правы; quoprimime.py нельзя использовать для кодирования произвольного юникода.

В качестве обходного пути вы можете использовать (по умолчанию) base64, опуская

c.body_encoding = email.charset.QP

Обратите внимание, что последняя версия quoprimime.py не вообще используйте _QUOPRI_BODY_MAP, поэтому использование последней версии Python может решить проблему.

person unutbu    schedule 22.02.2012
comment
Подозреваю, что не будет. Проблема, похоже, заключается в неправильном преобразовании в байты utf-8 перед применением кодировки с кавычками. Методы as_string и __str__ из email.message.Message должны быть объявлены устаревшими в пользу методов, которые вместо этого возвращают байты. Я предполагаю, что весь пакет электронной почты немного запутался в разнице между двоичным кодированием, выполненным в сообщении электронной почты, и кодированием, подразумеваемым использованием определенной системы кодирования символов. Эти два на самом деле являются отдельными понятиями, хотя они оба используют термин «кодирование». - person Omnifarious; 23.02.2012