Текст Юникода в строках

tekst junikoda v strokah Экскурсия по tkinter, часть 2

Причина всех этих сложностей заключается, конечно же, в том, что в мире Юникода мы не можем больше думать о «тексте», не задавая вопрос «какого он вида». Вообще текст может быть закодирован с использованием самых разных схем кодирования. В языке Python это обстоятельство неразрывно связано со строками str и может иметь отношение к строкам bytes, если они содержат кодированный текст. Строки str Юникода в языке Python — это просто строки, но вам придется принимать во внимание кодировки при записи строк в файлы и чтении их из файлов, а также при передаче их в библиотеки, накладывающие ограничения на кодирование текста.

Мы не будем рассматривать здесь проблемы кодирования Юникода во всех подробностях (подробное описание вы найдете в книге «Изучаем Python», а краткое упоминание о том, какое значение это имеет для файлов, — в главе 4), но коротко рассмотрим некоторые положения, чтобы увидеть — какое отношение они имеют к виджетам Text. Для начала вам следует запомнить, что проблемы с текстом ASCII не возникают лишь потому, что ASCII является подмножеством большинства схем кодирования Юникода. Однако данные, выходящие за диапазон представления 7-битовых символов ASCII, в разных схемах кодирования могут быть представлены разными байтами.

Например, следующие строки байтов должны декодироваться с использованием кодировки Latin-1 — попытка использовать кодировку по умолчанию или явно указанную кодировку, не соответствующую строке байтов, будет терпеть неудачу:

>>> b = bA\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

Оцените статью
Секреты программирования
Добавить комментарий