Документация Python неофициальный перевод
Содержание страницы

Руководство по UnicodeUnicode HOWTO

Версия

1.12

В данном руководстве рассматривается поддержка Python спецификации Unicode для представления текстовых данных, а также объясняются различные проблемы, с которыми часто сталкиваются при работе с Unicode.

Введение в UnicodeIntroduction to Unicode

ОпределенияDefinitions

Современным программам необходимо уметь работать с самыми разными символами. Приложения часто интернационализируются для вывода сообщений и результатов на различных языках, выбираемых пользователем; одна и та же программа может выводить сообщение об ошибке на английском, французском, японском, иврите или русском. Веб-контент может быть написан на любом из этих языков, а также может содержать различные эмодзи. Строковый тип Python использует стандарт Unicode для представления символов, что позволяет программам на Python работать со всеми этими различными возможными символами.

Unicode (https://www.unicode.org/) – это спецификация, цель которой – перечислить все символы, используемые в человеческих языках, и присвоить каждому символу уникальный код. Спецификации Unicode постоянно пересматриваются и обновляются для добавления новых языков и символов.

Символ – это минимально возможная составляющая текста. «A», «B», «C» и т.д. – все это разные символы. То же самое относится к «È» и «Í». Символы различаются в зависимости от языка или контекста, о котором идет речь. Например, существует символ «Римская единица» (Roman Numeral One) – «Ⅰ», который отличается от заглавной буквы «I». Обычно они выглядят одинаково, но это два разных символа с разными значениями.

Стандарт Unicode описывает, как символы представлены с помощью кодовых точек. Значение кодовой точки – целое число в диапазоне от 0 до 0x10FFFF (около 1,1 миллиона значений, из которых на данный момент присвоено примерно 110 тысяч). В стандарте и в этом документе кодовая точка записывается с помощью обозначения 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 – это кодовая точка, которая представляет некоторый конкретный символ; в данном случае она представляет символ «BLACK CHESS KNIGHT», «♞». В неформальном контексте это различие между кодовыми точками и символами иногда забывается.

Символ отображается на экране или на бумаге с помощью набора графических элементов, который называется глифом. Глиф для заглавной A, например, представляет собой две диагональные и одну горизонтальную черту, хотя точные детали зависят от используемого шрифта. Большинству кода на Python не нужно беспокоиться о глифах; определение правильного глифа для отображения обычно является задачей GUI-инструментария или рендерера шрифтов терминала.

КодировкиEncodings

Резюмируя предыдущий раздел: строка Unicode – это последовательность кодовых точек, которые являются числами от 0 до 0x10FFFF (1 114 111 в десятичной системе). Эту последовательность кодовых точек необходимо представить в памяти в виде набора кодовых единиц, а кодовые единицы затем отображаются на 8-битные байты. Правила преобразования строки Unicode в последовательность байтов называются кодировкой символов или просто кодировкой.

Первое, что приходит на ум, – использовать 32-битные целые числа в качестве кодовой единицы и применяемое в CPU представление этих чисел. В таком представлении строка «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

Такое представление интуитивно понятно, но его использование порождает ряд проблем.

  1. Оно не является переносимым: разные процессоры по-разному упорядочивают байты.

  2. Оно крайне неэкономно расходует место. В большинстве текстов кодовые точки в основном меньше 127 или 255, поэтому значительная часть места занята 0x00 байтами. Приведённая выше строка занимает 24 байта, тогда как в ASCII-представлении достаточно 6 байт. Увеличение расхода оперативной памяти не так критично (настольные компьютеры имеют гигабайты RAM, а строки редко бывают очень большими), однако четырёхкратное расширение при использовании дискового пространства и сетевой пропускной способности уже неприемлемо.

  3. Оно несовместимо с существующими C-функциями, такими как strlen(), поэтому пришлось бы использовать новое семейство функций для работы с широкими строками.

Поэтому данная кодировка используется редко; вместо неё выбирают другие, более эффективные и удобные, например UTF-8.

UTF-8 – одна из самых распространённых кодировок; Python часто использует её по умолчанию. Аббревиатура UTF расшифровывается как «Unicode Transformation Format» (формат преобразования Юникода), а цифра 8 означает, что в этой кодировке используются 8-битные значения. (Существуют также UTF-16 и UTF-32, но они применяются реже.) UTF-8 руководствуется следующими правилами:

  1. Если кодовая точка < 128, она представляется соответствующим значением байта.

  2. Если кодовая точка >= 128, она преобразуется в последовательность из двух, трёх или четырёх байт, причём каждый байт находится в диапазоне от 128 до 255.

UTF-8 обладает несколькими удобными свойствами:

  1. Она может обрабатывать любую кодовую точку Юникода.

  2. Строка Юникода преобразуется в последовательность байт, содержащую встроенные нулевые байты только там, где они представляют нулевой символ (U+0000). Это означает, что строки UTF-8 могут обрабатываться C-функциями, такими как strcpy(), и передаваться по протоколам, которые не допускают нулевые байты ни для чего, кроме маркеров конца строки.

  3. Любая строка ASCII также является корректным текстом UTF-8.

  4. UTF-8 довольно компактна; большинство часто используемых символов можно представить одним или двумя байтами.

  5. Если байты повреждены или потеряны, можно определить начало следующей UTF-8-кодовой точки и восстановить синхронизацию. Кроме того, маловероятно, что случайные 8-битные данные будут выглядеть как корректный UTF-8.

  6. UTF-8 – это байт-ориентированная кодировка. Она задаёт, что каждый символ представляется определённой последовательностью из одного или нескольких байт. Это позволяет избежать проблем с порядком байт, возникающих в целочисленных и слово-ориентированных кодировках, таких как UTF-16 и UTF-32, где последовательность байт меняется в зависимости от аппаратной платформы, на которой строка была закодирована.

СсылкиReferences

The Unicode Consortium site has character charts, a glossary, and PDF versions of the Unicode specification. Be prepared for some difficult reading. A chronology of the origin and development of Unicode is also available on the site.

On the Computerphile Youtube channel, Tom Scott briefly discusses the history of Unicode and UTF-8 (9 minutes 36 seconds).

To help understand the standard, Jukka Korpela has written an introductory guide to reading the Unicode character tables.

Another good introductory article was written by Joel Spolsky. If this introduction didn’t make things clear to you, you should try reading this alternate article before continuing.

Wikipedia entries are often helpful; see the entries for “character encoding” and UTF-8, for example.

Поддержка Юникода в PythonPython’s Unicode Support

Теперь, когда вы освоили основы Юникода, давайте рассмотрим возможности Python по работе с ним.

Тип строкиThe String Type

Начиная с 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'.
    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}"  # Использование имени символа
'\u0394'
>>> "\u0394"                          # Использование 16-битного шестнадцатеричного значения
'\u0394'
>>> "\U00000394"                      # Использование 32-битного шестнадцатеричного значения
'\u0394'

Кроме того, строку можно создать методом decode() объекта bytes. Этот метод принимает аргумент encoding (кодировка), например UTF-8, и опционально аргумент errors (реакция на ошибки).

Аргумент errors определяет реакцию, когда входную строку невозможно преобразовать согласно правилам кодировки. Допустимые значения этого аргумента: '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'

Encodings are specified as strings containing the encoding’s name. Python comes with roughly 100 different encodings; see the Python Library Reference at Standard Encodings for a list. Some encodings have multiple names; for example, 'latin-1', 'iso_8859_1' and '8859’ are all synonyms for the same encoding.

Односимвольные строки Юникода можно также создать с помощью встроенной функции chr(). Она принимает целое число и возвращает строку Юникода длиной 1, содержащую соответствующую кодовую точку. Обратная операция – встроенная функция ord(), которая принимает односимвольную строку Юникода и возвращает значение кодовой точки:

>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344

Преобразование в байтыConverting to Bytes

Противоположным методом для bytes.decode() является str.encode(), который возвращает bytes представление строки Unicode, закодированное в требуемой кодировке.

Параметр errors такой же, как и параметр метода decode(), но поддерживает несколько дополнительных обработчиков. Помимо 'strict', 'ignore' и 'replace' (который в данном случае вставляет знак вопроса вместо некодируемого символа), есть также 'xmlcharrefreplace' (вставляет ссылку на символ XML), backslashreplace (вставляет управляющую последовательность \uNNNN) и namereplace (вставляет управляющую последовательность \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'&#40960;abcd&#1972;'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'

Низкоуровневые функции для регистрации и доступа к доступным кодировкам находятся в модуле codecs. Реализация новых кодировок также требует понимания модуля codecs. Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно слишком низкоуровневые для комфортной работы, а написание новых кодировок является специализированной задачей, поэтому этот модуль не будет рассматриваться в данном HOWTO.

Литералы Unicode в исходном коде PythonUnicode Literals in Python Source Code

В исходном коде Python конкретные кодовые точки Unicode можно записать с помощью управляющей последовательности \u, за которой следуют четыре шестнадцатеричные цифры, задающие кодовую точку. Управляющая последовательность \U аналогична, но ожидает восемь шестнадцатеричных цифр, а не четыре:

>>> s = "a\xac\u1234\u20ac\U00008000"
... #     ^^^^ двузначная шестнадцатеричная escape-последовательность
... #         ^^^^^^ четырёхзначная Unicode escape-последовательность
... #                     ^^^^^^^^^^ восьмизначная Unicode escape-последовательность
>>> [ord(c) for c in s]
[97, 172, 4660, 8364, 32768]

Использование управляющих последовательностей для кодовых точек больше 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 для получения дополнительной информации.

Свойства UnicodeUnicode Properties

Спецификация 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))

# Получить числовое значение второго символа
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 для списка кодов категорий.

Сравнение строкComparing Strings

Unicode вносит некоторые сложности в сравнение строк, потому что один и тот же набор символов может быть представлен разными последовательностями кодовых точек. Например, буква «ê» может быть представлена как одна кодовая точка U+00EA, или как U+0065 U+0302, что является кодовой точкой для «e», за которой следует кодовая точка для «КОМБИНИРУЕМОГО ЦИРКУМФЛЕКСА». Эти представления будут давать одинаковый вывод при печати, но одна строка имеет длину 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))

При запуске выводится:

$ python3 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())

# Пример использования
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 для обсуждения и примера.)

Регулярные выражения UnicodeUnicode Regular Expressions

Регулярные выражения, поддерживаемые модулем 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].

СсылкиReferences

Некоторые хорошие альтернативные обсуждения поддержки Unicode в Python:

Тип str описан в справочнике по библиотеке Python по адресу Text Sequence Type – str.

Документация модуля unicodedata.

Документация модуля codecs.

Марк-Андре Лембург выступил с презентацией «Python and Unicode» (слайды в PDF) на EuroPython 2002. Слайды дают отличный обзор архитектуры поддержки Unicode в Python 2 (где тип строки Unicode называется unicode, а литералы начинаются с u).

Чтение и запись данных UnicodeReading and Writing Unicode Data

После написания кода, работающего с данными 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(). Это работает через open(): encoding и errors параметры, которые интерпретируются так же, как в 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’, чтобы автоматически пропускать метку, если она присутствует.

Имена файлов в UnicodeUnicode filenames

Большинство современных операционных систем поддерживают имена файлов содержащие произвольные символы 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.

Обратите внимание, что в большинстве случаев для этих API достаточно просто использовать Unicode. API для байтов следует применять только в системах, где могут встречаться не декодируемые имена файлов; сейчас это практически только Unix-системы.

Советы по написанию программ с поддержкой UnicodeTips for Writing Unicode-aware Programs

В этом разделе приводятся некоторые рекомендации по написанию программного обеспечения, работающего с Unicode.

Самый важный совет:

Программное обеспечение должно работать внутри только со строками Unicode, декодируя входные данные как можно раньше и кодируя вывод только в конце.

При попытке написать функции обработки, которые принимают как строки Unicode, так и байтовые строки, программа окажется уязвима для ошибок везде, где комбинируются два разных типа строк. Автоматического кодирования или декодирования не происходит: если выполнить, например, str + bytes, будет возбуждено TypeError.

При использовании данных из веб-браузера или другого ненадёжного источника распространённый приём – проверка строки на недопустимые символы перед использованием в генерируемой командной строке или сохранением в базе данных. Если это делается, следует проверять декодированную строку, а не закодированные байтовые данные; некоторые кодировки могут обладать интересными свойствами, такими как отсутствие биективности или полной совместимости с ASCII. Это особенно важно, если входные данные также задают кодировку, так как злоумышленник может выбрать хитрый способ скрыть вредоносный текст в закодированном потоке байтов.

Преобразование между кодировками файловConverting Between File Encodings

Класс StreamRecoder может прозрачно преобразовывать между кодировками, принимая поток, возвращающий данные в кодировке №1, и ведя себя как поток, возвращающий данные в кодировке №2.

Например, если есть входной файл f в кодировке Latin-1, его можно обернуть с помощью StreamRecoder, чтобы возвращать байты, закодированные в UTF-8:

new_f = codecs.StreamRecoder(f,
    # кодер/декодер: используется read() для кодирования результатов и
    # с помощью write() для декодирования своего ввода.
    codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),

    # читатель/писатель: используется для чтения и записи в поток.
    codecs.getreader('latin-1'), codecs.getwriter('latin-1') )

Файлы в неизвестной кодировкеFiles in an Unknown Encoding

Что делать, если нужно внести изменения в файл, но кодировка файла неизвестна? Если известно, что кодировка совместима с ASCII и требуется только просмотреть или изменить части в ASCII, можно открыть файл с обработчиком ошибок surrogateescape:

with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
    data = f.read()

# внести изменения в строку 'data'

with open(fname + '.new', 'w',
          encoding="ascii", errors="surrogateescape") as f:
    f.write(data)

Обработчик ошибок surrogateescape декодирует все не-ASCII байты как кодовые точки в специальном диапазоне от U+DC80 до U+DCFF. Затем эти кодовые точки снова превращаются в те же байты, когда обработчик ошибок surrogateescape используется для кодирования данных и записи их обратно.

СсылкиReferences

Одна из секций доклада Mastering Python 3 Input/Output на PyCon 2010 (Дэвид Бизли) посвящена обработке текста и работе с двоичными данными.

В слайдах PDF презентации Марка-Андре Лембурга «Writing Unicode-aware Applications in Python» рассматриваются вопросы кодировок символов, а также интернационализация и локализация приложений. Эти слайды охватывают только Python 2.x.

The Guts of Unicode in Python – доклад Бенджамина Петерсона на PyCon 2013, в котором обсуждается внутреннее представление Unicode в Python 3.3.

БлагодарностиAcknowledgements

Первоначальный черновик этого документа написал Эндрю Кухлинг. Впоследствии он был доработан Александром Белопольским, Георгом Брандлем, Эндрю Кухлингом и Эцио Мелотти.

Благодарности следующим людям, которые указали на ошибки или предложили улучшения: Эрик Араужо, Николас Бастин, Ник Коглан, Мариус Гедминас, Кент Джонсон, Кен Круглер, Марк-Андре Лембург, Мартин фон Лёвис, Терри Дж. Риди, Сергей Сторчака, Эрик Сан, Чад Уиттакр, Грэм Уайдман.