Руководство по 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 — это кодовая точка, которая представляет определённый символ; в данном случае это символ «ЧЁРНЫЙ КОНЬ ШАХМАТНЫЙ», ‘♞’. В неформальных контекстах это различие между кодовыми точками и символами иногда игнорируется.
Символ отображается на экране или на бумаге набором графических элементов, который называется глифом. Например, глиф заглавной буквы А состоит из двух диагональных черт и горизонтальной черты, хотя точные детали будут зависеть от используемого шрифта. Большинству кодов 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 (https://www.unicode.org) есть таблицы символов, глоссарий и PDF-версии спецификации Unicode. Будьте готовы к сложному чтению. Хронология происхождения и развития Unicode также доступна на сайте.
На канале YouTube Computerphile Том Скотт кратко рассказывает об истории Unicode и UTF-8 (9 минут 36 секунд).
Для лучшего понимания стандарта Юкка Корпела написал вводное руководство по чтению таблиц символов Unicode.
Ещё одна хорошая вводная статья была написана Джоэлом Спольски. Если данное введение не прояснило ситуацию, вы можете попробовать прочитать эту альтернативную статью перед продолжением.
Статьи в Википедии часто бывают полезными; см. статьи о «кодировке символов» и UTF-8, например.
Поддержка Unicode в Python
Теперь, когда вы ознакомились с основами Unicode, мы можем рассмотреть возможности Unicode в Python.
Тип Строка
Начиная с 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-последовательности в строковых литералах. (В зависимости от вашей системы вместо 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' (просто исключить символ из результата 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 в запрошенной кодировке.
Параметр ошибки такой же, как параметр метода 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' — «Символ, другой». См. раздел General Category Values документации базы данных символов Unicode для получения списка кодов категорий.
Сравнение строк
Unicode добавляет некоторую сложность к сравнениям строк, потому что один и тот же набор символов может быть представлен разными последовательностями кодов символов. Например, буква «ê» может быть представлена одним кодом U+00EA или как U+0065 U+0302, что соответствует коду «e» плюс коду «COMBINING CIRCUMFLEX ACCENT». Эти варианты дадут одинаковый результат при печати, но один — строка длиной 1, а другой — длиной 2.
Одним из инструментов для сравнения строк без учёта регистра является метод строки casefold(), который преобразует строку в форму без учёта регистра в соответствии с алгоритмом, описанным в стандарте Unicode. Этот алгоритм имеет специальную обработку для символов, таких как немецкая буква «ß» (код 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))
При выполнении выводится:
$ 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 для обсуждения и примера.)
Уникод регулярные выражения
Регулярные выражения, поддерживаемые модулем 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 соответствует широкому разнообразию символов Юникода, но только [a-zA-Z0-9_] в байтах или если указан флаг re.ASCII, а \s соответствует либо символам пробелов Юникода, либо [ \t\n\r\f\v].
Ссылки
Вот некоторые хорошие альтернативные обсуждения поддержки Юникода в Python:
- Обработка текстовых файлов в Python 3, автор Ник Коглэн.
- Практический Юникод, презентация Неда Батчельдера на PyCon 2012.
Тип str описан в справочнике по библиотеке Python по адресу Тип текстовой последовательности — str.
Документация модуля unicodedata.
Марк-Андре Лембюрг прочитал презентацию «Python и Юникод» (PDF слайды) на EuroPython 2002. Слайды представляют собой превосходный обзор дизайна функций Юникода Python 2 (где тип строки Юникода называется unicode, а литералы начинаются с u).
Чтение и запись данных Юникода
После написания кода, работающего с данными Юникода, возникает следующая проблема — ввод/вывод. Как получить строки Юникода в вашу программу и как преобразовать Юникод в формат, подходящий для хранения или передачи?
Возможно, вам не придётся ничего делать, в зависимости от источников ввода и мест назначения вывода; вы должны проверить, поддерживают ли библиотеки, используемые в вашем приложении, Юникод в родном виде. Например, парсеры XML часто возвращают данные Юникода. Многие реляционные базы данных также поддерживают столбцы с значениями Юникода и могут возвращать значения Юникода из запроса SQL.
Данные Юникода обычно преобразуются в определённое кодирование перед записью на диск или отправкой по сокету. Можно сделать всю работу самостоятельно: открыть файл, прочитать из него объект байтов с 8 битами и преобразовать байты с помощью bytes.decode(encoding). Однако ручное выполнение не рекомендуется.
Одна из проблем — многобайтовая природа кодировок; один символ Юникода может быть представлен несколькими байтами. Если вы хотите прочитать файл произвольными блоками (скажем, 1024 или 4096 байтов), вам необходимо написать код обработки ошибок, чтобы поймать случай, когда к концу блока прочитана только часть байтов, кодирующих один символ Юникода. Одним из решений было бы считать весь файл в память и затем выполнить декодирование, но это помешает работать с очень большими файлами; если вам нужно прочитать файл размером 2 ГБ, вам потребуется 2 ГБ оперативной памяти. (На самом деле больше, так как, по крайней мере, на некоторое время вам нужно будет иметь как закодированную строку, так и её версию Юникода в памяти.)
Решение заключается в использовании интерфейса декодирования низкого уровня для обработки случаев частичных кодовых последовательностей. Работа по реализации этого уже выполнена за вас: встроенная функция open() может вернуть объект, подобный файлу, который предполагает, что содержимое файла находится в указанном кодировании, и принимает параметры Юникода для методов, таких как read() и write(). Это работает через параметры encoding и errors функции open(), которые интерпретируются так же, как в str.encode() и bytes.decode().
Чтение данных Юникода из файла, таким образом, просто:
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]))
Символ Юникода U+FEFF используется в качестве маркера порядка байтов (BOM) и часто записывается как первый символ файла для помощи в автоматическом определении порядка байтов файла. Некоторые кодировки, такие как UTF-16, ожидают наличия BOM в начале файла; при использовании такой кодировки BOM будет автоматически записан в качестве первого символа и будет молча удалён при чтении файла. Существуют варианты этих кодировок, такие как «utf-16-le» и «utf-16-be» для кодировок с малым и большим порядком байтов, которые определяют конкретный порядок байтов и не пропускают BOM.
В некоторых областях также принято использовать «BOM» в начале файлов, закодированных в UTF-8; это название вводит в заблуждение, так как UTF-8 не зависит от порядка байтов. Этот маркер просто объявляет, что файл закодирован в UTF-8. Для чтения таких файлов используйте кодек «utf-8-sig», чтобы автоматически пропустить этот маркер, если он есть.
Имена файлов Юникода
Большинство операционных систем, которые используются сегодня, поддерживают имена файлов, содержащие произвольные символы Юникода. Обычно это реализуется путём преобразования строки Юникода в некоторое кодирование, которое варьируется в зависимости от системы. Сегодня Python сходится к использованию UTF-8: Python на MacOS использует UTF-8 уже несколько версий, а Python 3.6 перешёл на использование UTF-8 и в Windows. В системах Unix будет только кодирование файловой системы, если вы установили переменные окружения LANG или LC_CTYPE; если нет, то по-прежнему используется кодировка UTF-8.
Функция sys.getfilesystemencoding() возвращает используемую в вашей системе кодировку, в случае если вам нужно выполнить кодирование вручную, но нет большой необходимости этим заниматься. При открытии файла для чтения или записи вы обычно можете просто указать строку Юникода в качестве имени файла, и она будет автоматически преобразована в правильную кодировку для вас:
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
f.write('blah\n')
Функции в модуле os, такие как os.stat(), также будут принимать имена файлов Юникода.
Функция os.listdir() возвращает имена файлов, что вызывает проблему: нужно ли возвращать версию имен файлов в Юникоде или нужно ли возвращать байты, содержащие закодированные версии? os.listdir() может делать и то, и другое, в зависимости от того, предоставили ли вы путь к каталогу в виде байтов или строки Юникода. Если вы передадите строку Юникода в качестве пути, имена файлов будут декодированы с использованием кодировки файловой системы, и будет возвращён список строк Юникода, а при передаче байтового пути имена файлов будут возвращены в виде байтов. Например, предположим, что по умолчанию кодировка файловой системы — 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, а второй список содержит версии в Юникоде.
Обратите внимание, что в большинстве случаев вы можете просто продолжать использовать Юникод с этими API. API байтов следует использовать только в системах, где могут присутствовать нераспознаваемые имена файлов; это в основном относится к системам Unix.
Советы по написанию программ, осознающих Юникод
Этот раздел содержит несколько рекомендаций по написанию программного обеспечения, работающего с Юникодом.
Самый важный совет:
Внутренне программное обеспечение должно работать только со строками Юникода, декодируя входные данные как можно скорее и кодируя выходные данные только в конце.
Если вы попытаетесь написать функции обработки, которые принимают как строки Юникода, так и байтовые строки, вы обнаружите, что ваша программа уязвима к ошибкам в местах, где вы комбинируете два разных типа строк. Автоматического кодирования или декодирования нет: если вы, например, 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-слайды презентации Марка-Андре Лембурга «Написание приложений, осознающих Юникод, на Python» посвящены вопросам кодирования символов и способам локализации и интернационализации приложения. Эти слайды охватывают только Python 2.x.
«Внутренности Юникода в Python» — доклад на PyCon 2013, автор Бенджамин Питерсон, посвящённый внутреннему представлению Юникода в Python 3.3.
Благодарности
Первоначатный черновик этого документа был написан Эндрю Куклинг. С тех пор он был переработан Александром Белопольским, Георгом Брандлом, Эндрю Куклингем и Эцио Мелотти.
Благодарим следующих людей, которые отметили ошибки или предложили замечания по этой статье: Эрик Араухо, Николас Бастин, Ник Коглэн, Мариус Гедиминас, Кент Джонсон, Кен Крюглер, Марк-Андре Лембург, Мартин фон Ловис, Терри Дж. Риди, Сергей Сторчака, Эрик Сан, Чед Уайткрейк, Грэхэм Уайдмен.
© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/howto/unicode.html