Причина всех этих сложностей заключается, конечно же, в том, что в мире Юникода мы не можем больше думать о «тексте», не задавая вопрос «какого он вида». Вообще текст может быть закодирован с использованием самых разных схем кодирования. В языке Python это обстоятельство неразрывно связано со строками str и может иметь отношение к строкам bytes, если они содержат кодированный текст. Строки str Юникода в языке Python — это просто строки, но вам придется принимать во внимание кодировки при записи строк в файлы и чтении их из файлов, а также при передаче их в библиотеки, накладывающие ограничения на кодирование текста.
Мы не будем рассматривать здесь проблемы кодирования Юникода во всех подробностях (подробное описание вы найдете в книге «Изучаем Python», а краткое упоминание о том, какое значение это имеет для файлов, — в главе 4), но коротко рассмотрим некоторые положения, чтобы увидеть — какое отношение они имеют к виджетам Text. Для начала вам следует запомнить, что проблемы с текстом ASCII не возникают лишь потому, что ASCII является подмножеством большинства схем кодирования Юникода. Однако данные, выходящие за диапазон представления 7-битовых символов ASCII, в разных схемах кодирования могут быть представлены разными байтами.
Например, следующие строки байтов должны декодироваться с использованием кодировки Latin-1 — попытка использовать кодировку по умолчанию или явно указанную кодировку, не соответствующую строке байтов, будет терпеть неудачу:
>>> b = b’A\xc4B\xe4C’ # эти байты — текст в кодировке latin-1
>>> b
b’A\xc4B\xe4C’
>>> s = b.decode(‘utf8’)
UnicodeDecodeError: ‘utf8’ codec can’t decode bytes in position 1-2: invalid dat…
>>> s = b.decode()
UnicodeDecodeError: ‘utf8’ codec can’t decode bytes in position 1-2: invalid dat…
>>> s = b.decode(‘latin1’)
>>> s
‘ AABaC’
Декодировав байты в строку Юникода, вы сможете «преобразовать» ее обратно в строку байтов с применением различных кодировок. В действительности при этом будет произведено преобразование в альтернативное двоичное представление, которое позднее мы опять сможем декодировать в строку — по существу строка Юникода не относится к «типу Юникод», к нему могут относиться только двоичные данные:
>>> s.encode(‘latin-1’)
b’A\xc4B\xe4C’
>>> s.encode(‘utf-8’)
b’A\xc3\x84B\xc3\xa4C’
>>> s.encode(‘utf-16’)
b’\xff\xfeA\x00\xc4\x00B\x00\xe4\x00C\x00’
>>> s.encode(‘ascii’)
UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘\xc4’ in position 1: o…
Обратите внимание на последнюю операцию в этом примере: кодируемая строка должна быть совместима с используемой схемой кодирования, в противном случае будет возбуждено исключение. В данном случае диапазон ASCII оказался слишком узким для представления символов, полученных в результате декодирования из байтов в кодировке Latin-1. То есть вы можете преобразовать строку в различные (совместимые) двоичные представления, но тем не менее для декодирования этих данных обратно в строку в общем случае вам необходимо знать кодировку, использовавшуюся при кодировании:
>>> s.encode(‘utf-16’).decode(‘utf-16’)
‘ AABaC’
>>> s.encode(‘latin-1’).decode(‘latin-1’)
‘AABaC’
>>> s.encode(‘latin-1’).decode(‘utf-8’)
UnicodeDecodeError: ‘utf8’ codec can’t decode bytes in position 1-2: invalid dat…
>>> s.encode(‘utf-8’).decode(‘latin-1’)
UnicodeEncodeError: ‘charmap’ codec can’t encode character ‘\xc3’ in position 2:…
Снова обратите внимание на последнюю операцию. Технически кодирование кодовых пунктов (символов) Юникода в байты UTF-8 и обратное их декодирование с применением кодировки Latin-1 не возбуждает ошибку, но попытка вывести результат приводит к исключению: с точки зрения функции вывода он является зашифрованным мусором. Чтобы обеспечить необходимую точность, необходимо знать, какая кодировка применялась при создании двоичного представления:
>>> s
‘AABaC’
> >> x = s.encode(‘utf-8’).decode(‘utf-8’) # OK: кодировки совпадают
>>> x
‘AABaC’
> >> x = s.encode(‘latin-1’).decode(‘latin-1’) # можно использовать любые
> >> x # совместимые кодировки
‘AABaC’
> >> x = s.encode(‘utf-8’).decode(‘latin-1’) # декодирование выполняется,
> >> x # но получается мусор
UnicodeEncodeError: ‘charmap’ codec can’t encode character ‘\xc3’ in position 2:…
> >> len(s), len(x) # уже не та же самая строка
(5, 7)
>>> s.encode(‘utf-8’) # не те же самые кодовые пункты
b’A\xc3\x84B\xc3\xa4C’
>>> x.encode(‘utf-8’)
b’A\xc3\x83\xc2\x84B\xc3\x83\xc2\xa4C’
>>> s.encode(‘latin-1’)
b’A\xc4B\xe4C’
>>> x.encode(‘latin-1’)
b’A\xc3\x84B\xc3\xa4C’
Самое интересное, что после применения несовпадающих кодировок иногда оригинальную строку все еще можно восстановить. Если снова закодировать полученный мусор в кодировку Latin-1 (8-битовые символы) и затем декодировать с применением корректной кодировки, оригинальная строка будет восстановлена (благодаря этому обстоятельству в некоторых ситуациях вы можете все поправить, если при первой попытке данные были декодированы неправильно):
>>> s
‘ AABaC’
>>> s.encode(‘utf-8’).decode(‘latin-1’)
UnicodeEncodeError: ‘charmap’ codec can’t encode character ‘\xc3’ in position 2:…
>>> s.encode(‘utf-8’).decode(‘latin-1’).encode(‘latin-1’)
b’A\xc3\x84B\xc3\xa4C’
>>> s.encode(‘utf-8’).decode(‘latin-1’).encode(‘latin-1’).decode(‘utf-8’) ‘AABaC’
> >> s.encode(‘utf-8’).decode(‘latin-1’).encode(‘latin-1’).decode(‘utf-8’) == s True
С другой стороны, для декодирования данных можно использовать различные кодировки, при условии, что они совместимы с кодировкой данных — при использовании кодировок ASCII, UTF-8 и Latin-1, например, операция декодирования текста ASCII возвращает один и тот же результат:
> >> ‘spam’.encode(‘utf8’).decode(‘latin1’)
‘spam’
> >> ‘spam’.encode(‘latin1’).decode(‘ascii’)
‘spam’
Важно помнить, что декодированная строка никак не зависит от кодировки, с применением которой она была получена. После декодирования понятие кодировки не может применяться к строке — она является обычной последовательностью символов Юникода («кодовых пунктов»). Таким образом, заботиться о кодировках необходимо только в точках передачи данных между программой и файлами:
>>> s
‘AABaC’
> >> s.encode(‘utf-16’).decode(‘utf-16’) == s.encode(‘latin-1’).decode(‘latin-1’) True
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011