Руководство по Unicode
- Версия
-
1.12
В данном руководстве рассматривается поддержка Python спецификации Unicode для представления текстовых данных и объясняются различные проблемы, с которыми люди обычно сталкиваются при работе с Unicode.
Введение в Unicode
Определения
Современные программы должны уметь обрабатывать широкий спектр символов. Приложения часто локализуются, чтобы отображать сообщения и выводить данные на разных языках, которые пользователь может выбрать; одна и та же программа может выводить сообщение об ошибке на английском, французском, японском, иврите или русском языке. Веб-контент может быть написан на любом из этих языков и также может включать различные символы эмодзи. Тип строки Python использует стандарт Unicode для представления символов, что позволяет программам Python работать со всеми этими различными возможными символами.
Unicode (https://www.unicode.org/) — это спецификация, которая стремится перечислить каждый символ, используемый человеческими языками, и присвоить каждому символу свой уникальный код. Спецификации Unicode постоянно пересматриваются и обновляются, чтобы добавить новые языки и символы.
Символ — это наименьшая возможная составляющая текста. «A», «B», «C» и т. д. — это все разные символы. Также таковыми являются «È» и «Í». Символы варьируются в зависимости от языка или контекста, о котором идёт речь. Например, существует символ «римская цифра один», «Ⅰ», который отличается от заглавной буквы «I». Обычно они выглядят одинаково, но это два разных символа, имеющих разное значение.
Стандарт Unicode описывает, как символы представлены кодовыми точками. Значение кодовой точки — целое число в диапазоне от 0 до 0x10FFFF (около 1,1 миллиона значений, фактическое назначенное количество меньше этого). В стандарте и в этом документе кодовая точка записывается с помощью обозначения U+265E , что означает символ со значением 0x265e (9822 в десятичной системе).
Стандарт Unicode содержит множество таблиц, перечисляющих символы и соответствующие им кодовые точки:
0061 'a'; LATIN SMALL LETTER A
0062 'b'; LATIN SMALL LETTER B
0063 'c'; LATIN SMALL LETTER C
...
007B '{'; LEFT CURLY BRACKET
...
2167 'Ⅷ'; ROMAN NUMERAL EIGHT
2168 'Ⅸ'; ROMAN NUMERAL NINE
...
265E '♞'; BLACK CHESS KNIGHT
265F '♟'; BLACK CHESS PAWN
...
1F600 '😀'; GRINNING FACE
1F609 '😉'; WINKING FACE
...
Строго говоря, эти определения подразумевают, что бессмысленно говорить «это символ U+265E». U+265E — это кодовая точка, представляющая определённый символ; в данном случае она представляет символ «ЧЁРНЫЙ КОНЬ ШАХМАТНЫЙ», «♞». В неформальных контекстах это различие между кодовыми точками и символами иногда забывается.
Символ представлен на экране или на бумаге набором графических элементов, которые называются глифом. Например, глиф заглавной буквы A состоит из двух диагональных и одной горизонтальной черты, хотя точные детали зависят от используемого шрифта. Большинству кодов Python не нужно беспокоиться о глифах; определение правильного глифа для отображения обычно является задачей инструментария графического интерфейса или рендерера шрифтов терминала.
Кодировки
Подводя итог предыдущему разделу: строка Unicode — это последовательность кодовых точек, которые представляют собой числа от 0 до 0x10FFFF (1 114 111 в десятичной системе). Эта последовательность кодовых точек должна быть представлена в памяти как набор кодовых единиц, а кодовые единицы затем отображаются на 8-битные байты. Правила преобразования строки Unicode в последовательность байтов называются кодировкой символов или просто кодировкой.
Первой кодировкой, о которой вы можете подумать, является использование 32-битных целых чисел в качестве кодовой единицы, а затем использование представления процессором 32-битных целых чисел. В этом представлении строка «Python» может выглядеть так:
P y t h o n 0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Это представление прямолинейно, но его использование создает ряд проблем.
- Это не переносимо; разные процессоры упорядочивают байты по-разному.
- Это очень расточительно в плане использования памяти. В большинстве текстов большинство кодовых точек меньше 127 или меньше 255, поэтому много места занимают
0x00байты. Приведённая строка занимает 24 байта по сравнению с 6 байтами, необходимыми для представления ASCII. Повышенное использование оперативной памяти не имеет особого значения (настольные компьютеры имеют гигабайты оперативной памяти, а строки обычно не бывают такими большими), но увеличение использования дискового пространства и пропускной способности сети в 4 раза неприемлемо. - Это несовместимо с существующими функциями C, такими как
strlen(), поэтому необходимо будет использовать новую семью функций для работы с широкими строками.
Поэтому эта кодировка не очень часто используется, и люди вместо этого выбирают другие кодировки, которые более эффективны и удобны, такие как UTF-8.
UTF-8 — одна из наиболее часто используемых кодировок, и Python часто использует её по умолчанию. UTF означает «Unicode Transformation Format», а «8» означает, что в кодировке используются 8-битные значения. (Существуют также кодировки UTF-16 и UTF-32, но они используются реже, чем UTF-8). UTF-8 использует следующие правила:
- Если кодовая точка меньше 128, она представлена соответствующим значением байта.
- Если кодовая точка больше или равна 128, она преобразуется в последовательность из двух, трёх или четырёх байтов, где каждый байт последовательности находится в диапазоне от 128 до 255.
UTF-8 обладает несколькими удобными свойствами:
- Он может обрабатывать любые кодовые точки Unicode.
- Строка Unicode преобразуется в последовательность байтов, содержащую вставленные нулевые байты только там, где они представляют нулевой символ (U+0000). Это означает, что строки UTF-8 могут обрабатываться функциями C, такими как
strcpy(), и передаваться по протоколам, которые не могут обрабатывать нулевые байты для чего-либо, кроме маркеров конца строки. - Строка ASCII текста также является допустимым текстом UTF-8.
- UTF-8 довольно компактен; большинство часто используемых символов можно представить одним или двумя байтами.
- Если байты повреждены или потеряны, можно определить начало следующей кодовой точки, закодированной в UTF-8, и синхронизироваться. Также маловероятно, что случайные 8-битные данные будут выглядеть как допустимый UTF-8.
- UTF-8 — это кодировка, ориентированная на байты. Кодировка определяет, что каждый символ представлен определённой последовательностью одного или нескольких байтов. Это устраняет проблемы с порядком байтов, которые могут возникнуть при кодировках, ориентированных на целые числа и слова, таких как UTF-16 и UTF-32, где последовательность байтов изменяется в зависимости от оборудования, на котором была закодирована строка.
Ссылки
На сайте Консорциума Unicode есть таблицы символов, глоссарий и PDF-версии спецификации Unicode. Будьте готовы к трудному чтению. Хронология происхождения и развития Unicode также доступна на сайте.
На канале Computerphile на YouTube Том Скотт кратко рассказывает об истории Unicode и UTF-8 (9 минут 36 секунд).
Для лучшего понимания стандарта Юкка Корпела написал вводное руководство по чтению таблиц символов Unicode.
Другая хорошая вводная статья была написана Джоэлом Сполки. Если это введение не прояснило для вас ситуацию, попробуйте прочитать эту альтернативную статью, прежде чем продолжать.
Статьи в Википедии часто бывают полезными; например, см. статьи о «кодировке символов» и UTF-8.
Поддержка Юникода в Python
Теперь, когда вы ознакомились с основами Юникода, мы можем рассмотреть возможности Юникода в Python.
Тип Строка
Начиная с Python 3.0, тип str языка содержит символы Юникода, что означает, что любая строка, созданная с помощью "unicode rocks!", 'unicode
rocks!', или синтаксиса строк в тройных кавычках, хранится как Юникод.
По умолчанию кодировка исходного кода Python — UTF-8, поэтому вы можете просто включить символ Юникода в строковый литерал:
try:
with open('/tmp/input.txt', 'r') as f:
...
except OSError:
# 'File not found' error message.
print("Fichier non trouvé")
Примечание: Python 3 также поддерживает использование символов Юникода в идентификаторах:
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
f.write("test\n")
Если вы не можете ввести определенный символ в своем редакторе или хотите сохранить исходный код только в формате ASCII по какой-либо причине, вы также можете использовать escape-последовательности в строковых литералах. (В зависимости от вашей системы вместо u-escape вы можете увидеть фактический символ заглавной дельты.)
>>> "\N{GREEK CAPITAL LETTER DELTA}" # Using the character name
'\u0394'
>>> "\u0394" # Using a 16-bit hex value
'\u0394'
>>> "\U00000394" # Using a 32-bit hex value
'\u0394'
Кроме того, можно создать строку, используя метод decode() объекта bytes. Этот метод принимает аргумент кодировка, такой как UTF-8, и необязательный аргумент ошибки.
Аргумент ошибки определяет ответ, когда входная строка не может быть преобразована в соответствии с правилами кодировки. Допустимые значения для этого аргумента — 'strict' (вызвать исключение UnicodeDecodeError), 'replace' (использовать U+FFFD, REPLACEMENT CHARACTER), 'ignore' (просто исключить символ из результата Юникода) или 'backslashreplace' (вставляет escape-последовательность \xNN).
>>> b'\x80abc'.decode("utf-8", "strict")
Traceback (most recent call last):
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'
Кодировки указываются как строки, содержащие имя кодировки. Python поставляется примерно с 100 различными кодировками; см. Справочник по библиотеке Python по адресу Стандартные кодировки для получения списка. Некоторые кодировки имеют несколько имен; например, 'latin-1', 'iso_8859_1' и '8859 — синонимы одной и той же кодировки.
Строки Юникода длиной в один символ также можно создать с помощью встроенной функции chr(), которая принимает целые числа и возвращает строку Юникода длиной 1, содержащую соответствующий код символа. Обратной операцией является встроенная функция ord(), которая принимает строку Юникода длиной в один символ и возвращает значение кода символа:
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344
Преобразование в байты
Противоположным методом bytes.decode() является str.encode(), который возвращает представление строки Юникода в формате bytes, закодированной в запрошенной кодировке.
Параметр ошибки такой же, как и параметр метода decode(), но поддерживает несколько дополнительных возможных обработчиков. Помимо 'strict', 'ignore', и 'replace' (в данном случае вставка вопросительного знака вместо некодируемого символа), также есть 'xmlcharrefreplace' (вставка XML-ссылки на символ), backslashreplace (вставка escape-последовательности \uNNNN ) и namereplace (вставка escape-последовательности \N{...}).
Следующий пример демонстрирует различные результаты:
>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'
Низкоуровневые функции для регистрации и доступа к доступным кодировкам находятся в модуле codecs. Реализация новых кодировок также требует понимания модуля codecs. Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно более низкого уровня, чем это удобно, а написание новых кодировок — это специализированная задача, поэтому модуль не будет рассматриваться в этом руководстве.
Литералы Юникода в исходном коде Python
В исходном коде Python определенные коды символов Юникода можно записать, используя escape-последовательность \u, за которой следуют четыре шестнадцатеричных цифры, задающие код символа. Escape-последовательность \U аналогична, но ожидает восемь шестнадцатеричных цифр, а не четыре:
>>> s = "a\xac\u1234\u20ac\U00008000" ... # ^^^^ two-digit hex escape ... # ^^^^^^ four-digit Unicode escape ... # ^^^^^^^^^^ eight-digit Unicode escape >>> [ord(c) for c in s] [97, 172, 4660, 8364, 32768]
Использование escape-последовательностей для кодов символов, больших 127, приемлемо в небольших объёмах, но становится раздражающим, если вы используете много символов с диакритическими знаками, как в программе с сообщениями на французском или другом языке с диакритическими знаками. Вы также можете собрать строки, используя встроенную функцию chr(), но это ещё более утомительно.
В идеале вы хотели бы иметь возможность писать литералы в кодировке вашего языка. Тогда вы могли бы редактировать исходный код Python с помощью своего любимого редактора, который будет отображать символы с диакритическими знаками естественным образом и использовать правильные символы во время выполнения.
Python по умолчанию поддерживает запись исходного кода в UTF-8, но вы можете использовать практически любую кодировку, если объявите используемую кодировку. Это делается путём включения специального комментария в первой или второй строке исходного файла:
#!/usr/bin/env python # -*- coding: latin-1 -*- u = 'abcdé' print(ord(u[-1]))
Синтаксис вдохновлен нотацией Emacs для задания переменных, локальных для файла. Emacs поддерживает множество различных переменных, но Python поддерживает только ‘coding’. Символы -*- указывают Emacs, что комментарий особый; для Python они не имеют значения, но являются соглашением. Python ищет coding: name или coding=name в комментарии.
Если вы не включите такой комментарий, по умолчанию будет использоваться кодировка UTF-8, как уже упоминалось. См. также PEP 263 для получения дополнительной информации.
Свойства Юникода
Спецификация Юникода включает базу данных информации о кодах символов. Для каждого определенного кода символа информация включает имя символа, его категорию, числовое значение, если применимо (для символов, представляющих числовые понятия, такие как римские цифры, дроби, такие как одна треть и четыре пятых, и т.д.). Также есть свойства, связанные с отображением, такие как способ использования кода символа в тексте с двунаправленной ориентацией.
Следующая программа отображает некоторую информацию о нескольких символах и выводит числовое значение одного определенного символа:
import unicodedata
u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)
for i, c in enumerate(u):
print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
print(unicodedata.name(c))
# Get numeric value of second character
print(unicodedata.numeric(u[1]))
При выполнении она выводит:
0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE 1 0bf2 No TAMIL NUMBER ONE THOUSAND 2 0f84 Mn TIBETAN MARK HALANTA 3 1770 Lo TAGBANWA LETTER SA 4 33af So SQUARE RAD OVER S SQUARED 1000.0
Коды категорий — это аббревиатуры, описывающие природу символа. Они сгруппированы в категории, такие как «Буква», «Число», «Пунктуация» или «Символ», которые в свою очередь разбиты на подкатегории. Взяв коды из приведенного выше вывода, 'Ll' означает «Буква, строчная», 'No' означает «Число, другое», 'Mn' означает «Диакритический знак, без пропусков» и 'So' означает «Символ, другой». См. раздел «Общие категории значений» в документации базы данных символов Юникода для списка кодов категорий.
Сравнение строк
Юникод добавляет некоторую сложность при сравнении строк, поскольку один и тот же набор символов может быть представлен различными последовательностями кодов символов. Например, буква «ê» может быть представлена одним кодом U+00EA или как U+0065 U+0302, что является кодом для «e» в сочетании с кодом для «COMBINING CIRCUMFLEX ACCENT». При печати они выведут одинаковый результат, но одна строка длиной 1, а другая — длиной 2.
Одним из инструментов для сравнения строк без учёта регистра является метод строки casefold(), который преобразует строку в регистронезависимую форму в соответствии с алгоритмом, описанным в стандарте Юникода. Этот алгоритм имеет специальную обработку для символов, таких как немецкая буква «ß» (код U+00DF), которая становится парой строчных букв «ss».
>>> street = 'Gürzenichstraße' >>> street.casefold() 'gürzenichstrasse'
Вторым инструментом является функция unicodedata модуля normalize(), которая преобразует строки в одну из нескольких нормальных форм, где буквы, за которыми следуют диакритические знаки, заменяются на единые символы. normalize() может использоваться для выполнения сравнений строк, которые не будут ошибочно сообщать о неравенстве, если две строки используют диакритические знаки по-разному:
import unicodedata
def compare_strs(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(s1) == NFD(s2)
single_char = 'ê'
multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print('length of first string=', len(single_char))
print('length of second string=', len(multiple_chars))
print(compare_strs(single_char, multiple_chars))
При выполнении она выводит:
$ python3 compare-strs.py length of first string= 1 length of second string= 2 True
Первый аргумент функции normalize() — строка, задающая желаемую форму нормализации, которая может быть одной из «NFC», «NFKC», «NFD» и «NFKD».
Стандарт Юникода также определяет, как проводить сравнение без учёта регистра:
import unicodedata
def compare_caseless(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())
# Example usage
single_char = 'ê'
multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print(compare_caseless(single_char, multiple_chars))
Это выведет True. (Почему NFD() вызывается дважды? Потому что есть несколько символов, которые заставляют casefold() вернуть ненормализованную строку, поэтому результат нужно нормализовать ещё раз. См. раздел 3.13 стандарта Юникода для обсуждения и примера.)
Регулярные выражения Unicode
Регулярные выражения, поддерживаемые модулем re, могут быть предоставлены в виде байтов или строк. Некоторые специальные последовательности символов, такие как \d и \w , имеют различное значение в зависимости от того, предоставлен ли шаблон в виде байтов или строки. Например, \d будет соответствовать символам [0-9] в байтах, но в строках будет соответствовать любому символу, который входит в категорию 'Nd'.
В этом примере строка содержит число 57, записанное как тайскими, так и арабскими цифрами:
import re p = re.compile(r'\d+') s = "Over \u0e55\u0e57 57 flavours" m = p.search(s) print(repr(m.group()))
При выполнении \d+ будет соответствовать тайским цифрам и выводить их. Если вы передадите флаг re.ASCII функции compile(), \d+ будет соответствовать подстроке “57” вместо этого.
Аналогично, \w соответствует широкому спектру символов Unicode, но только [a-zA-Z0-9_] в байтах или если предоставлен флаг re.ASCII, и \s будет соответствовать либо символам пробела Unicode, либо [ \t\n\r\f\v].
Ссылки
Некоторые хорошие альтернативные обсуждения поддержки Unicode в Python:
- Обработка текстовых файлов в Python 3, Н. Коглана.
- Практический Unicode, презентация Неда Бэтчелдера на PyCon 2012.
Тип str описан в справочнике по библиотеке Python по адресу Тип текстовой последовательности — str.
Документация модуля unicodedata.
Марк-Андре Лембург выступил с презентацией под названием «Python и Unicode» (PDF-слайды) на EuroPython 2002. Слайды являются отличным обзором дизайна функций Unicode Python 2 (где тип строки Unicode называется unicode , а литералы начинаются с u).
Чтение и запись данных Unicode
После написания кода, работающего с данными Unicode, следующая проблема — ввод/вывод. Как получить строки Unicode в программу и как преобразовать Unicode в формат, подходящий для хранения или передачи?
Возможно, вам ничего не нужно делать, в зависимости от источников ввода и мест назначения вывода. Проверьте, поддерживают ли библиотеки, используемые в вашем приложении, Unicode напрямую. Например, парсеры XML часто возвращают данные Unicode. Многие реляционные базы данных также поддерживают столбцы с данными Unicode и могут возвращать значения Unicode из запроса SQL.
Данные Unicode обычно преобразуются в определённое кодирование перед записью на диск или отправкой по сокету. Можно выполнить все действия самостоятельно: открыть файл, прочитать из него объект байтов 8-битного типа и преобразовать байты с помощью bytes.decode(encoding). Однако ручного подхода не рекомендуется.
Одна проблема — многобайтовость кодировок; один символ Unicode может быть представлен несколькими байтами. Если вы хотите читать файл произвольными блоками (скажем, 1024 или 4096 байтов), вам нужно написать код обработки ошибок, чтобы поймать случай, когда в конце блока прочитана только часть байтов, кодирующих один символ Unicode. Одним из решений было бы считать весь файл в память, а затем выполнить декодирование, но это не позволяет работать с очень большими файлами; если вам нужно прочитать файл размером 2 ГБ, вам потребуется 2 ГБ оперативной памяти. (На самом деле больше, так как некоторое время вам нужно будет хранить как закодированную строку, так и её версию Unicode в памяти.)
Решение заключается в использовании низкоуровневого интерфейса декодирования, чтобы поймать случай частичной кодировки. Работа по реализации этого уже сделана за вас: встроенная функция open() может вернуть объектоподобный файл, предполагающий, что содержимое файла находится в заданном кодировании, и принимает параметры Unicode для методов, таких как read() и write(). Это работает через параметры encoding и errors функции open(), которые интерпретируются так же, как и в str.encode() и bytes.decode().
Чтение Unicode из файла поэтому просто:
with open('unicode.txt', encoding='utf-8') as f:
for line in f:
print(repr(line))
Также можно открыть файлы в режиме обновления, позволяющем как читать, так и записывать:
with open('test', encoding='utf-8', mode='w+') as f:
f.write('\u4500 blah blah blah\n')
f.seek(0)
print(repr(f.readline()[:1]))
Символ Unicode U+FEFF используется как метка порядка байтов (BOM) и часто записывается как первый символ файла для помощи в автоматическом определении порядка байтов файла. Некоторые кодировки, такие как UTF-16, ожидают наличия BOM в начале файла; при использовании такой кодировки BOM будет автоматически записан как первый символ и будет молча удалён при чтении файла. Есть варианты этих кодировок, такие как «utf-16-le» и «utf-16-be» для кодировок little-endian и big-endian, которые задают определённый порядок байтов и не пропускают BOM.
В некоторых областях также принято использовать «BOM» в начале UTF-8-кодированных файлов; это название вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Метка просто объявляет, что файл закодирован в UTF-8. Для чтения таких файлов используйте кодек «utf-8-sig», чтобы автоматически пропустить метку, если она есть.
Имена файлов Unicode
Большинство используемых сегодня операционных систем поддерживают имена файлов, содержащие произвольные символы Unicode. Обычно это реализуется путём преобразования строки Unicode в некоторое кодирование, которое различается в зависимости от системы. Сегодня Python стремится к использованию UTF-8: Python на MacOS использует UTF-8 уже несколько версий, а Python 3.6 перешёл на использование UTF-8 и в Windows. В системах Unix будет только кодировка файловой системы. если вы задали переменные среды LANG или LC_CTYPE; если нет, по умолчанию используется UTF-8.
Функция sys.getfilesystemencoding() возвращает кодировку для использования в текущей системе, если вы хотите выполнить кодирование вручную, но особой причины для этого нет. При открытии файла для чтения или записи вы обычно можете просто указать строку Unicode в качестве имени файла, и она будет автоматически преобразована в правильную кодировку для вас:
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
f.write('blah\n')
Функции модуля os, такие как os.stat(), также будут принимать имена файлов Unicode.
Функция os.listdir() возвращает имена файлов, что вызывает проблему: должна ли она возвращать версию Unicode имён файлов или байты, содержащие закодированные версии? os.listdir() может делать и то, и другое, в зависимости от того, предоставили ли вы путь к каталогу в виде байтов или строки Unicode. Если вы передадите строку Unicode в качестве пути, имена файлов будут декодированы с использованием кодировки файловой системы, и будет возвращён список строк Unicode, а передача байтового пути вернёт имена файлов в виде байтов. Например, предположим, что по умолчанию кодировка файловой системы — UTF-8, выполнение следующей программы:
fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()
import os
print(os.listdir(b'.'))
print(os.listdir('.'))
выведет следующий вывод:
$ python listdir-test.py [b'filename\xe4\x94\x80abc', ...] ['filename\u4500abc', ...]
Первый список содержит имена файлов, закодированные в UTF-8, а второй — версии Unicode.
Обратите внимание, что во многих случаях вы можете просто продолжать использовать Unicode с этими API. API байтов следует использовать только в системах, где могут присутствовать имена файлов, не подлежащие декодированию; это в основном только системы Unix сейчас.
Советы по написанию программ, осознающих Unicode
Этот раздел содержит некоторые рекомендации по написанию программного обеспечения, которое обрабатывает Unicode.
Самый важный совет:
Программное обеспечение должно работать только со строками Unicode во внутренней части, декодировать данные ввода как можно скорее и кодировать выходные данные только в конце.
Если вы попытаетесь написать функции обработки, которые принимают как строки Unicode, так и строки байтов, вы обнаружите, что ваша программа уязвима к ошибкам, где бы вы ни объединили два разных типа строк. Автоматического кодирования или декодирования нет: если вы сделаете, например, str + bytes, будет поднято исключение TypeError.
При использовании данных, полученных из веб-браузера или другого ненадежного источника, распространённой техникой является проверка наличия недопустимых символов в строке перед использованием строки в сгенерированной командной строке или хранением её в базе данных. Если вы делаете это, будьте осторожны, проверяйте декодированную строку, а не закодированные данные байтов; некоторые кодировки могут иметь интересные свойства, такие как не быть взаимно однозначными или не быть полностью совместимыми с ASCII. Это особенно верно, если входные данные также указывают кодировку, так как злоумышленник может выбрать хитрый способ скрыть вредоносный текст в потоке байтов, закодированных данным способом.
Преобразование между кодировками файлов
Класс StreamRecoder может прозрачно преобразовывать между кодировками, принимая поток, возвращающий данные в кодировке #1, и ведя себя как поток, возвращающий данные в кодировке #2.
Например, если у вас есть входной файл f в кодировке Latin-1, вы можете обернуть его StreamRecoder, чтобы возвращать байты, закодированные в UTF-8:
new_f = codecs.StreamRecoder(f,
# en/decoder: used by read() to encode its results and
# by write() to decode its input.
codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),
# reader/writer: used to read and write to the stream.
codecs.getreader('latin-1'), codecs.getwriter('latin-1') )
Файлы в неизвестной кодировке
Что вы можете сделать, если вам нужно внести изменения в файл, но вы не знаете кодировку файла? Если вы знаете, что кодировка совместима с ASCII и хотите только просмотреть или изменить части ASCII, вы можете открыть файл с обработчиком ошибок surrogateescape:
with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
data = f.read()
# make changes to the string 'data'
with open(fname + '.new', 'w',
encoding="ascii", errors="surrogateescape") as f:
f.write(data)
Обработчик ошибок surrogateescape будет декодировать любые байты, не являющиеся ASCII, как кодовые точки в специальном диапазоне от U+DC80 до U+DCFF. Затем эти кодовые точки будут преобразованы обратно в те же байты при использовании обработчика ошибок surrogateescape для кодирования данных и записи их обратно.
Ссылки
В одной части Mastering Python 3 Input/Output, доклада PyCon 2010 Дэвида Бисли, обсуждаются обработка текста и обработка двоичных данных.
В PDF-слайдах презентации Марка-Андре Лембурга «Написание программ, осознающих Unicode, на Python» обсуждаются вопросы кодировок символов, а также как интернационализировать и локализовать приложение. Эти слайды охватывают только Python 2.x.
The Guts of Unicode in Python — доклад PyCon 2013 Бенджамина Петерсона, посвящённый внутреннему представлению Unicode в Python 3.3.
Благодарности
Первоначатный черновик этого документа был написан Эндрю Куклином. С тех пор он был переработан Александром Белопольским, Георгом Брандлом, Эндрю Куклином и Эцио Мелотти.
Спасибо следующим людям, которые отметили ошибки или предложили замечания по этой статье: Эрик Араухо, Николас Бастин, Ник Коглан, Мариюс Гедиминас, Кент Джонсон, Кен Крюглера, Марк-Андре Лембюрг, Мартин фон Ловис, Терри Дж. Риди, Сергей Сторчака, Эрик Сон, Чэд Уитакр, Грэхэм Вайдеман.
© 2001–2023 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.10/howto/unicode.html