Руководство по 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 (9 822 в десятичной системе счисления).
Стандарт 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. Увеличение использования оперативной памяти не слишком важно (настольные компьютеры располагают гигабайтами оперативной памяти, а строки обычно не бывают такими большими), но четырёхкратное увеличение использования дискового пространства и пропускной способности сети недопустимо. - Оно несовместимо с существующими функциями C, такими как
strlen(), поэтому пришлось бы использовать новое семейство функций для широких строк.
Поэтому эта кодировка используется редко, и вместо неё выбирают другие, более эффективные и удобные кодировки, например UTF-8.
UTF-8 — одна из наиболее распространённых кодировок, и Python часто использует её по умолчанию. UTF означает «Unicode Transformation Format» (формат преобразования Unicode), а «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 есть таблицы символов, глоссарий и версии спецификации Unicode в формате PDF. Будьте готовы к тому, что читать их будет непросто. На сайте также доступна хронология возникновения и развития Unicode.
Том Скотт кратко рассказывает об истории Unicode и UTF-8 на YouTube-канале Computerphile (9 минут 36 секунд).
Чтобы помочь разобраться в стандарте, Юкка Корпела написал вводное руководство по чтению таблиц символов Unicode.
Джоэл Спольски написал ещё одну хорошую вводную статью. Если это введение не помогло вам разобраться, попробуйте прочитать эту альтернативную статью, прежде чем продолжить.
Часто полезны статьи в Википедии; например, см. статьи о «кодировке символов» и UTF-8.
Поддержка Unicode в Python
Теперь, когда вы знакомы с основами Unicode, можно рассмотреть возможности Python для работы с Unicode.
Строковый тип
Начиная с Python 3.0, тип языка str содержит символы Unicode. Это значит, что любая строка, созданная с помощью "unicode rocks!", 'unicode
rocks!' или синтаксиса многострочных строк, хранится в формате Unicode.
Кодировка исходного кода Python по умолчанию — UTF-8, поэтому вы можете просто включить символ Unicode в строковый литерал:
try:
with open('/tmp/input.txt', 'r') as f:
...
except OSError:
# 'File not found' error message.
print("Fichier non trouvé")
Кстати, Python 3 также поддерживает использование символов Unicode в идентификаторах:
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
f.write("test\n")
Если в редакторе нельзя ввести нужный символ или по какой-то причине вы хотите, чтобы исходный код содержал только символы ASCII, можно использовать escape-последовательности в строковых литералах. (В зависимости от системы вместо escape-последовательности u вы можете увидеть настоящий глиф заглавной дельты.)
>>> "\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. Этот метод принимает аргумент encoding, например UTF-8, и необязательный аргумент errors.
Аргумент errors задаёт реакцию на случай, если входную строку нельзя преобразовать в соответствии с правилами кодировки. Допустимые значения этого аргумента: 'strict' (вызывает исключение UnicodeDecodeError), 'replace' (использует U+FFFD, REPLACEMENT CHARACTER), 'ignore' (просто удаляет символ из результата Unicode) или '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 — синонимы одной и той же кодировки.
Односимвольные строки Unicode также можно создать с помощью встроенной функции chr(), которая принимает целые числа и возвращает строку Unicode длиной 1, содержащую соответствующую кодовую точку. Обратную операцию выполняет встроенная функция ord(), которая принимает односимвольную строку Unicode и возвращает значение кодовой точки:
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344
Преобразование в байты
Обратный метод к bytes.decode() — это str.encode(), который возвращает представление строки Unicode в виде bytes, закодированное в указанной кодировке encoding.
Параметр errors такой же, как у метода 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. Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно слишком низкоуровневые для удобной работы, а создание новых кодировок — специализированная задача, поэтому в этом руководстве модуль рассматриваться не будет.
Литералы Unicode в исходном коде Python
В исходном коде Python конкретные кодовые точки Unicode можно записывать с помощью 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.
Свойства Unicode
Спецификация Unicode включает базу данных со сведениями о кодовых точках. Для каждой определённой кодовой точки в этих сведениях указаны название и категория символа, а также числовое значение, если оно применимо (например, для символов, представляющих числовые понятия, таких как римские цифры, дроби вроде одной трети и четырёх пятых и т. д.). Существуют также свойства, связанные с отображением, например правила использования кодовой точки в двунаправленном тексте.
Следующая программа выводит информацию о нескольких символах и числовое значение одного конкретного символа:
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' — «Символ, прочее». Список кодов категорий см. в разделе «Значения общих категорий» документации по базе данных символов Unicode.
Сравнение строк
Unicode усложняет сравнение строк, поскольку один и тот же набор символов может быть представлен разными последовательностями кодовых точек. Например, букву «ê» можно представить одной кодовой точкой U+00EA или последовательностью U+0065 U+0302, где кодовая точка буквы «e» следует за кодовой точкой «КОМБИНИРУЕМЫЙ ЦИРКУМФЛЕКС». При печати они будут выглядеть одинаково, но одна строка будет иметь длину 1, а другая — длину 2.
Для сравнения без учёта регистра можно использовать строковый метод casefold(), который преобразует строку в форму без учёта регистра согласно алгоритму, описанному в стандарте Unicode. Этот алгоритм особым образом обрабатывает такие символы, как немецкая буква «ß» (кодовая точка U+00DF), которая преобразуется в пару строчных букв «ss».
>>> street = 'Gürzenichstraße' >>> street.casefold() 'gürzenichstrasse'
Второй инструмент — функция normalize() модуля unicodedata, которая преобразует строки в одну из нескольких нормальных форм, заменяя буквы, за которыми следует комбинируемый символ, одиночными символами. Функцию 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))
При запуске выводится:
$ python compare-strs.py length of first string= 1 length of second string= 2 True
Первый аргумент функции normalize() — это строка, задающая нужную форму нормализации: «NFC», «NFKC», «NFD» или «NFKD».
Стандарт Unicode также определяет, как выполнять сравнение без учёта регистра:
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.)
Регулярные выражения 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.
Документация по модулю codecs.
Марк-Андре Лембург выступил с докладом «Python и Unicode» (слайды в формате PDF) на EuroPython 2002. Слайды дают превосходный обзор устройства поддержки Unicode в Python 2 (где строковый тип Unicode называется unicode, а литералы начинаются с u).
Чтение и запись данных Unicode
После того как вы написали код для работы с данными Unicode, возникает следующая задача — ввод-вывод. Как передать строки Unicode в программу и преобразовать Unicode в формат, подходящий для хранения или передачи?
Возможно, вам вообще ничего не придётся делать — это зависит от источников входных данных и мест назначения выходных данных; проверьте, поддерживают ли используемые в приложении библиотеки Unicode изначально. Например, XML-анализаторы часто возвращают данные Unicode. Многие реляционные базы данных также поддерживают столбцы со значениями Unicode и могут возвращать значения Unicode из SQL-запроса.
Данные Unicode обычно преобразуются в определённую кодировку перед записью на диск или отправкой через сокет. Всю работу можно выполнить самостоятельно: открыть файл, прочитать из него 8-битный объект bytes и преобразовать байты с помощью 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» для кодировок с прямым и обратным порядком байтов, которые задают определённый порядок байтов и не пропускают 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 будет использоваться filesystem encoding, если заданы переменные среды 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; если передать путь в виде байтов, имена файлов будут возвращены в виде байтов. Например, если считать, что кодировка filesystem encoding по умолчанию — 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.
Обратите внимание: в большинстве случаев для работы с этими API достаточно использовать Unicode. 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 для кодирования данных и записи их обратно эти кодовые точки будут преобразованы в те же байты.
Ссылки
В одном из разделов выступления «Освоение ввода-вывода в Python 3», представленного Дэвидом Бизли на PyCon 2010, обсуждаются обработка текста и работа с бинарными данными.
В PDF-слайдах презентации Марка-Андре Лембурга «Написание приложений с поддержкой Unicode в Python» рассматриваются вопросы кодировок символов, а также интернационализации и локализации приложений. В этих слайдах рассматривается только Python 2.x.
«Внутреннее устройство Unicode в Python» — выступление Бенджамина Петерсона на PyCon 2013, посвящённое внутреннему представлению Unicode в Python 3.3.
Благодарности
Первоначальный вариант этого документа написал Эндрю Кучлинг. Позднее его доработали Александр Белопольский, Георг Брандл, Эндрю Кучлинг и Эцио Мелотти.
Благодарим следующих людей, которые сообщили об ошибках или предложили улучшения для этой статьи: Эрик Аро, Николас Бастин, Ник Коглан, Мариус Гедминас, Кент Джонсон, Кен Крюглер, Марк-Андре Лембург, Мартин фон Лёви, Терри Дж. Риди, Сергей Сторчака, Эрик Сан, Чад Уитакр, Грэм Уайдеман.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/unicode.html