Следующая проблема в пакете email, связанная с поддержкой Юникода, в некоторой степени даже противоречит модели программирования на языке Python в целом: типы данных содержимого объектов сообщений могут отличаться в зависимости от того, как они извлекаются. Это особенно усложняет реализацию программ, которые выполняют обход и обработку частей сообщения с содержимым.
В частности, метод get_payload объекта Message, который мы использовали выше, принимает необязательный аргумент decode, управляющий автоматическим декодированием данных в формате MIME (например, Base64, uuencode, quoted-printable). Если в этом аргументе передать число 1 (или True), содержимое будет декодироваться при извлечении, если это необходимо. Это настолько удобно при обработке сложных сообщений с произвольными частями, что в этом аргументе обычно всегда передается значение 1 . Двоичные части сообщения, как правило, всегда кодируются в формат MIME, но даже текстовые части могут быть представлены в одном из форматов MIME, таком как Base64, если значения их байтов выходят за рамки стандартов электронной почты. Некоторые типы текста Юникода, например, требуют кодирования в формат MIME.
В результате метод get_payload, обычно возвращающий строки str для текстовых частей str, будет возвращать строки bytes, если его аргумент decode имеет значение True — даже когда заранее известно, что обрабатываемая часть сообщения по своей природе является текстовой. Если этот аргумент не используется, тип содержимого будет зависеть от его исходного типа: str или bytes. Поскольку в Python 3.X не допускается смешивать типы str и bytes в операциях, клиенты, которые предусматривают сохранение результатов в файлах или их обработку как текста, должны учитывать эти различия. Выполним некоторый программный код для иллюстрации:
>>> from email.message import Message
>>> m = Message()
>>> m[‘From’] = ‘Lancelot’
>>> m.set_payload(‘Line?…’)
>>> m[‘From’]
‘Lancelot’
>>> m.get_payload() # str, если содержимое имеет тип str
‘Line?…’
>>> m.get_payload(decode=1) # bytes, (то же, что и decode=True) выполняется
b‘Line?…’ # декодирование MIME, если необходимо
Сочетание этих различий в типах возвращаемых данных со строгим разделением типов str/bytes в Python 3.X может вызывать проблемы обработки результатов при невнимательном отношении к декодированию:
>>> m.get_payload(decode=True) + ‘spam’ # нельзя смешивать в 3.X!
TypeError: can’t concat bytes to str
(TypeError: невозможно объединить bytes и str)
>>> m.get_payload(decode=True).decode() + ‘spam’ # преобразовать,
‘Line?…spam‘ # если необходимо
Чтобы вы могли понять смысл этих примеров, запомните, что к тексту сообщения электронной почты применяются два разных понятия термина «кодировка»:
• Ко диров ки MIME элек трон ной поч ты, такие как Base64, uuencode и quoted-printable, применяются к двоичным данным и другому необычному содержимому, чтобы обеспечить возможность их передачи в тексте сообщения электронной почты.
• Ко диров ки Юни ко да текстовых строк в целом применяются к тексту сообщения, а также к его частям, и могут потребоваться для текстовых частей после декодирования из формата MIME.
Пакет email обрабатывает кодировки MIME автоматически, когда при анализе и извлечении содержимого указывается аргумент decode=1 или когда создается сообщение, содержащее непечатаемые части, однако сценарии по-прежнему должны принимать во внимание кодировки Юникода из-за важных отличий между строковыми типами в Python 3.X. Например, следующая инструкция выполняет первое декодирование из формата MIME, а второе — в строку символов Юникода:
m.get_payload(decode=True).decode() # в bytes через MIME, затем в str
Даже когда аргумент decode не используется, тип содержимого все равно может отличаться при сохранении в разных форматах:
>>> m = Message(); m.set_payload(‘spam’); m.get_payload() # извлекаемый тип
‘spam’ # соответствует
>>> m = Message(); m.set_payload(b’spam’); m.get_payload() # сохраненному
b‘spam‘ # типу
То же самое справедливо для текстовых подклассов MIME (хотя, как мы увидим далее в этом разделе, мы не можем передавать строки bytes их конструкторам, чтобы принудительно создать двоичное содержимое):
>>> from email.mime.text import MIMEText
>>> m = MIMEText(‘Line…?’)
>>> m[‘From’] = ‘Lancelot’
>>> m[‘From’]
‘Lancelot’
>>> m.get_payload()
‘Line…?’
>>> m.get_payload(decode=1)
b‘Line…?’
К сожалению, тот факт, что в настоящее время содержимое может быть типа str или bytes, не только противоречит нейтральной к типам модели программирования на языке Python, но и усложняет программный код — в сценариях может оказаться необходимым выполнять преобразования, когда контекст требует использования того или иного типа. Например, библиотеки графических интерфейсов могут позволять использовать оба типа, но операции сохранения файлов и создания содержимого веб-страниц могут оказаться менее гибкими. В наших примерах программ мы будем обрабатывать содержимое как строки типа bytes везде, где это возможно, и декодировать в строки типа str в случаях, где это необходимо, используя информацию о кодировке, доступную для прикладного интерфейса заголовков, описываемого в следующем разделе.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011