Первая проблема в пакете email, связанная с поддержкой Юникода в Python3.1, является практически непреодолимым препятствием в некоторых контекстах: строки bytes, подобные тем, которые возвращает модуль poplib при извлечении сообщения электронной почты, должны декодироваться в строки str перед анализом их с помощью пакета email. К сожалению, из-за недостатка информации о том, как декодировать байты сообщения в Юникод, в некоторых клиентах этого пакета может потребоваться обеспечить определение всех кодировок, использованных в сообщении, перед тем, как анализировать его. В самых тяжелых случаях, помимо электронной почты, — в которых требуется использовать данные смешанных типов, текущая версия пакета вообще не сможет применяться. Эта проблема демонстрируется ниже:
> >> text # из предыдущего примера в этом разделе
‘Content-Type: multipart/mixed; boundary="===============1574823535=="\nMI
> >> btext = text.encode()
>>> btext
b’Content-Type: multipart/mixed; boundary="===============1574823535=="\nM
> >> msg = Parser().parsestr(text) # Parser ожидает получить строку Юникода,
> >> msg = Parser().parsestr(btext) # но poplib возвращает строку bytes!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python31\lib\email\parser.py", line 82, in parsestr
return self.parse(StringIO(text), headersonly=headersonly)
TypeError: initial_value must be str or None, not bytes
(TypeError: initial_value должно иметь тип str или None, а не bytes)
> >> msg = Parser().parsestr(btext.decode()) # кодировка по умолчанию
> >> msg = Parser().parsestr(btext.decode(‘utf8′)) # текст ascii (по умолч.) >>> msg = Parser().parsestr(btext.decode(‘latin1′)) # текст ascii остается >>> msg = Parser().parsestr(btext.decode(‘ascii‘)) # одинаковым
# во всех 3 случаях
Это не самое идеальное решение, так как пакет email, ориентированный на работу со строками bytes, мог бы обеспечить более непосредственное применение кодировок к сообщениям. Однако, как уже упоминалось выше, пакет email в Python 3.1 недостаточно функционален из-за того, что он ориентирован на работу с типом str и из-за существенных отличий между текстом Юникода и строками байтов в Python 3.X. В данном случае объект, выполняющий анализ, должен был бы принимать строку bytes, а не ждать, что клиенты будут знать, как выполнить ее декодирование.
По этой причине в реализациях клиентов электронной почты, представленных в этой книге, предпринят упрощенный подход к декодированию байтов сообщений, которые необходимо проанализировать с помощью пакета email. В частности, сначала выполняется попытка декодировать полный текст сообщения с применением кодировки, указанной пользователем; в случае неудачи используются эвристические алгоритмы определения кодировки и, наконец, предпринимается попытка декодировать только заголовки сообщения.
Этого будет достаточно для примеров, но для более широкого применения данный прием может потребоваться расширить. В некоторых случаях кодировку можно определить другими способами, например проанализировав заголовки сообщения (если они присутствуют), выполнив структурный анализ байтов или запросив кодировку у пользователя. Попытка добавить подобные улучшения, действующие достаточно надежно, может оказаться слишком сложной для книжных примеров, но в любом случае для этой цели лучше всего использовать инструменты стандартной библиотеки.
На самом деле в настоящее время надежное декодирование текста сообщения может оказаться вообще невозможным, если для этого требуется проверить заголовки, — мы не сможем извлечь информацию о кодировке сообщения, не выполнив его анализ, но мы не сможем проанализировать сообщение с помощью пакета email в Python 3.1, пока не узнаем кодировку. То есть чтобы узнать кодировку, сценарию может потребоваться проанализировать сообщение, но, чтобы выполнить анализ, необходимо знать кодировку! Строки байтов, возвращаемые poplib, и строки символов Юникода, с которыми работает пакет email, в Python 3.1 имеют фундаментальные отличия. Даже внутри стандартной библиотеки изменения, внесенные в Python 3.X, создали проблему «курицы и яйца», которая никуда не делась даже спустя почти два года после выхода версии 3.0.
Кроме разработки собственного механизма анализа сообщений электронной почты или реализации других подобных по сложности подходов лучшим решением на сегодняшний день выглядит определение кодировки из пользовательских настроек и использование значений по умолчанию. Именно этот подход будет применяться в этом издании. Клиент PyMailGUI из главы 14, например, будет позволять указывать кодировку полного текста почтового сообщения для каждого сеанса в отдельности.
Настоящая проблема, конечно же, состоит в том, что электронная почта изначально усложнена необходимостью поддержки произвольных кодировок текста. Вдобавок к проблеме выбора кодировки полного текста сообщения по мере его анализа мы должны не ошибиться при выборе кодировок его отдельных текстовых компонентов — текстовых частей и заголовков. Двинемся дальше, чтобы увидеть, почему.
Подобная проблема в CGI-сценариях: следует также отметить, что проблема декодирования полного текста сообщения может оказаться не настолько существенной, как для некоторых клиентов пакета email. Оригинальные стандарты электронной почты предусматривают обмен текстом ASCII и требуют выполнять преобразование двоичных данных в формат MIME, поэтому большинство электронных писем наверняка будут корректно декодироваться с применением 7- или 8-битовой кодировки, такой как Latin-1.
Однако, как мы увидим в главе 15, перед серверными веб-сценариями, поддерживающими вы груз ку файлов по CGI, встает похожая и еще более непреодолимая проблема. Для анализа составных форм данных модуль CGI в языке Python использует пакет email. Пакет email требует декодировать данные в строки str для анализа. А сами данные, в свою очередь, могут представлять собой смесь текстовых и двоичных данных (включая простые двоичные данные, к которым не применялось преобразование в формат MIME, текст в любой кодировке и даже произвольные их комбинации). Поэтому в Python 3.1 операция отправки таких форм будет терпеть неудачу, если они будут включать двоичные или несовместимые текстовые файлы. Еще до того, как у сценария появится шанс вмешаться в происходящее, внутри модуля cgi будет возбуждено исключение ошибки декодирования Юникода или неверного типа.
Выгрузка по CGI работает в Python 2.X лишь по той простой причине, что тип str в этой версии может представлять и закодированный текст, и двоичные данные. Сохранения такого типа содержимого в двоичном файле в виде строки байтов в версии 2.X было достаточно для обработки произвольного текста и двоичных данных, таких как изображения. По той же причине в 2.X не возникает проблем с анализом сообщений электронной почты. Хорошо это или плохо, но разделение типов str/bytes в 3.X делает это обобщение невозможным.
Иными словами, в целом мы можем обойти требование пакета email и представлять для анализа почтовые сообщения в виде строк str, декодируя их с применением 8-битовой кодировки, однако эта проблема является весьма болезненной для всех сфер современного вебпрограммирования. Смотрите дополнительные подробности по этой теме в главе 15 и обязательно следите за изменениями, которые, возможно, уже будут реализованы к тому моменту, когда вы читаете эти строки.
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011