> **Источник:** https://python-all.ru/3.4/howto/unicode.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# Руководство по Unicode

| Версия: | 1.12 |
| --- | --- |

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

## Введение в Unicode

### История кодировок символов

В 1968 году был стандартизирован American Standard Code for Information Interchange, более известный под аббревиатурой ASCII. ASCII определил числовые коды для различных символов, причем числовые значения варьировались от 0 до 127. Например, строчная буква 'a' получила код 97.

ASCII был стандартом, разработанным в США, поэтому он определял только символы без диакритических знаков. Буква 'e' была, а 'é' или 'Í' – нет. Это означало, что языки, требующие диакритических знаков, не могли быть точно представлены в ASCII. (На самом деле отсутствие диакритических знаков имеет значение и для английского, в котором есть такие слова, как 'naïve' и 'café', а некоторые издательства требуют написания вроде 'coöperate'.)

Некоторое время люди просто писали программы, которые не отображали диакритические знаки. В середине 1980-х программа на Apple II BASIC, написанная носителем французского языка, могла содержать строки вроде этих:

```python
PRINT "MISE A JOUR TERMINEE"
PRINT "PARAMETRES ENREGISTRES"
```

Эти сообщения должны содержать диакритические знаки (terminée, paramètre, enregistrés) и они выглядят неправильно для того, кто понимает французский.

В 1980-х почти все персональные компьютеры были 8-битными, то есть байты могли хранить значения от 0 до 255. Коды ASCII доходили только до 127, поэтому некоторые машины присваивали значения от 128 до 255 символам с диакритическими знаками. Однако разные машины имели разные коды, что приводило к проблемам при обмене файлами. В конце концов появилось несколько часто используемых наборов значений для диапазона 128–255. Одни из них были настоящими стандартами, определёнными Международной организацией по стандартизации, а другие – *de facto* соглашениями, созданными той или иной компанией и получившими распространение.

255 символов – это не так много. Например, невозможно вместить и диакритические символы, используемые в Западной Европе, и кириллицу, используемую для русского языка, в диапазон 128–255, потому что таких символов больше 128.

Можно было записывать файлы, используя разные кодировки (все русские файлы в кодировке KOI8, все французские файлы в другой кодировке Latin1), но что, если нужно было написать французский документ, цитирующий русский текст? В 1980-х люди начали искать решение этой проблемы, и началась работа по стандартизации Unicode.

Изначально Unicode использовал 16-битные символы вместо 8-битных. 16 бит дают 2^16 = 65 536 различных значений, что позволяет представить множество символов из самых разных алфавитов; первоначальная цель заключалась в том, чтобы Unicode включал алфавиты всех языков человечества. Однако оказалось, что даже 16 бит недостаточно для достижения этой цели, и современная спецификация Unicode использует более широкий диапазон кодов: от 0 до 1 114 111 (`0x10FFFF` в 16-ричной системе).

Существует связанный стандарт ISO – ISO 10646. Unicode и ISO 10646 изначально были разными проектами, но спецификации были объединены в версии Unicode 1.1.

(Это обсуждение истории Unicode сильно упрощено. Точные исторические детали не обязательны для понимания того, как эффективно использовать Unicode, но если вам интересно, обратитесь к сайту консорциума Unicode, указанному в разделе «Ссылки», или к [статье в Википедии об Unicode](https://python-all.ru/3.4/howto/unicode.html) для получения дополнительной информации.)

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

**Символ** – это наименьший возможный компонент текста. 'A', 'B', 'C' и т.д. – всё это разные символы. Также как 'È' и 'Í'. Символы – это абстракции, и они варьируются в зависимости от языка или контекста. Например, символ ома (Ω) обычно рисуется очень похоже на заглавную букву омега (Ω) в греческом алфавите (в некоторых шрифтах они могут даже совпадать), но это два разных символа с разными значениями.

Стандарт Unicode описывает, как символы представляются с помощью **кодовых точек**. Кодовая точка – это целое число, обычно обозначаемое в 16-ричной системе. В стандарте кодовая точка записывается в нотации `U+12CA`, что означает символ со значением `0x12ca` (4 810 в десятичной). Стандарт Unicode содержит множество таблиц, перечисляющих символы и соответствующие им кодовые точки:

```text
0061    'a'; LATIN SMALL LETTER A
0062    'b'; LATIN SMALL LETTER B
0063    'c'; LATIN SMALL LETTER C
...
007B    '{'; LEFT CURLY BRACKET
```

Строго говоря, из этих определений следует, что бессмысленно говорить «это символ `U+12CA`». `U+12CA` – это кодовая точка, которая представляет некоторый конкретный символ; в данном случае она представляет символ «ETHIOPIC SYLLABLE WI». В неформальном контексте это различие между кодовыми точками и символами иногда забывается.

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

### Кодировки

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

Первая кодировка, которая может прийти в голову – это массив 32-битных целых чисел. В этом представлении строка «Python» будет выглядеть так:

```text
   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 байта по сравнению с 6 байтами, необходимыми для представления в ASCII. Увеличение использования оперативной памяти не так важно (настольные компьютеры имеют гигабайты RAM, а строки обычно не настолько велики), но увеличение использования диска и пропускной способности сети в 4 раза нетерпимо.
3. Она несовместима с существующими функциями C, такими как `strlen()`, поэтому пришлось бы использовать новое семейство функций для работы с широкими строками.
4. Многие интернет-стандарты определены в терминах текстовых данных и не могут обрабатывать содержимое с встроенными нулевыми байтами.

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

Кодировки не обязаны обрабатывать все возможные символы Unicode, и большинство кодировок этого не делают. Правила преобразования строки Unicode в кодировку ASCII, например, просты; для каждой кодовой точки:

1. Если кодовая точка \< 128, каждый байт равен значению кодовой точки.
2. Если кодовая точка равна 128 или больше, Unicode-строка не может быть представлена в этой кодировке. (В этом случае Python возбуждает исключение [`UnicodeEncodeError`](https://python-all.ru/3.4/library/exceptions.html#UnicodeEncodeError).)

Latin-1, также известная как ISO-8859-1, является аналогичной кодировкой. Кодовые точки Unicode 0–255 идентичны значениям Latin-1, поэтому преобразование в эту кодировку просто требует преобразования кодовых точек в байтовые значения; если встречается кодовая точка больше 255, строка не может быть закодирована в Latin-1.

Кодировки не обязательно должны быть простыми однозначными отображениями, как Latin-1. Рассмотрим EBCDIC от IBM, которая использовалась на мейнфреймах IBM. Значения букв не были в одном блоке: от 'a' до 'i' имели значения от 129 до 137, а от 'j' до 'r' – от 145 до 153. Если бы вы хотели использовать EBCDIC в качестве кодировки, вам, вероятно, пришлось бы использовать какую-нибудь таблицу поиска для выполнения преобразования, но это в значительной степени внутренняя деталь.

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

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

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

1. Она может обрабатывать любую кодовую точку Юникода.
2. Unicode-строка преобразуется в строку байтов, не содержащую встроенных нулевых байтов. Это позволяет избежать проблем с порядком байтов и означает, что строки UTF-8 могут обрабатываться функциями C, такими как `strcpy()`, и передаваться по протоколам, которые не могут обрабатывать нулевые байты.
3. Любая строка ASCII также является корректным текстом UTF-8.
4. UTF-8 довольно компактна; большинство часто используемых символов можно представить одним или двумя байтами.
5. Если байты повреждены или потеряны, можно определить начало следующей UTF-8-кодовой точки и восстановить синхронизацию. Кроме того, маловероятно, что случайные 8-битные данные будут выглядеть как корректный UTF-8.

### Ссылки

The [Unicode Consortium site](https://python-all.ru/3.4/howto/unicode.html) has character charts, a glossary, and PDF versions of the Unicode specification. Be prepared for some difficult reading. [A chronology](https://python-all.ru/3.4/howto/unicode.html) of the origin and development of Unicode is also available on the site.

To help understand the standard, Jukka Korpela has written [an introductory guide](https://python-all.ru/3.4/howto/unicode.html) to reading the Unicode character tables.

Another [good introductory article](https://python-all.ru/3.4/howto/unicode.html) 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](https://python-all.ru/3.4/howto/unicode.html)” and [UTF-8](https://python-all.ru/3.4/howto/unicode.html), for example.

## Поддержка Юникода в Python

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

### Тип строки

Начиная с Python 3.0, в языке есть тип [`str`](https://python-all.ru/3.4/library/stdtypes.html#str), который содержит символы Unicode. Это означает, что любая строка, созданная с помощью `"unicode rocks!"`, `'unicode rocks!'` или синтаксиса тройных кавычек, хранится как Unicode.

Кодировка исходного кода Python по умолчанию – UTF-8, поэтому можно просто включить символ Юникода в строковый литерал:

```python
try:
    with open('/tmp/input.txt', 'r') as f:
        ...
except OSError:
    # Сообщение об ошибке 'File not found'.
    print("Fichier non trouvé")
```

Можно использовать другую кодировку, отличную от UTF-8, поместив специально оформленный комментарий в первую или вторую строку исходного кода:

```python
# -*- coding: <encoding name> -*-
```

Попутное замечание: Python 3 также поддерживает использование символов Юникода в идентификаторах:

```python
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
    f.write("test\n")
```

Если вы не можете ввести нужный символ в редакторе или по какой-то причине хотите сохранить исходный код только в ASCII, можно использовать escape-последовательности в строковых литералах. (В зависимости от системы вместо u-escape может отображаться сама заглавная дельта.)

```python
>>> "\N{GREEK CAPITAL LETTER DELTA}"  # Использование имени символа
'\u0394'
>>> "\u0394"                          # Использование 16-битного шестнадцатеричного значения
'\u0394'
>>> "\U00000394"                      # Использование 32-битного шестнадцатеричного значения
'\u0394'
```

Кроме того, строку можно создать с помощью метода [`decode()`](https://python-all.ru/3.4/library/stdtypes.html#bytes.decode) объекта [`bytes`](https://python-all.ru/3.4/library/functions.html#bytes). Этот метод принимает аргумент *encoding*, например `UTF-8`, и опционально аргумент *errors*.

Аргумент *errors* задаёт реакцию, когда входную строку невозможно преобразовать в соответствии с правилами кодировки. Допустимые значения этого аргумента: `'strict'` (возбуждает исключение [`UnicodeDecodeError`](https://python-all.ru/3.4/library/exceptions.html#UnicodeDecodeError)), `'replace'` (использует `U+FFFD`, `REPLACEMENT CHARACTER`) или `'ignore'` (просто исключает символ из Unicode-результата). Следующие примеры показывают различия:

```python
>>> 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", "ignore")
'abc'
```

(В этом примере кода заменяющий символ Unicode заменён вопросительным знаком, поскольку на некоторых системах он может не отображаться.)

Кодировки задаются строками, содержащими имя кодировки. Python 3.2 поставляется примерно со 100 различными кодировками; см. [*Standard Encodings*](https://python-all.ru/3.4/library/codecs.html#standard-encodings) в Python Library Reference для полного списка. Некоторые кодировки имеют несколько имён; например, `'latin-1'`, `'iso_8859_1'` и `'8859`‘ – все синонимы одной и той же кодировки.

Односимвольные Unicode-строки также можно создать с помощью встроенной функции [`chr()`](https://python-all.ru/3.4/library/functions.html#chr), которая принимает целые числа и возвращает Unicode-строку длиной 1, содержащую соответствующую кодовую точку. Обратная операция – встроенная функция [`ord()`](https://python-all.ru/3.4/library/functions.html#ord), которая принимает односимвольную Unicode-строку и возвращает значение кодовой точки:

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

### Преобразование в байты

Противоположный метод [`bytes.decode()`](https://python-all.ru/3.4/library/stdtypes.html#bytes.decode) – это [`str.encode()`](https://python-all.ru/3.4/library/stdtypes.html#str.encode), который возвращает [`bytes`](https://python-all.ru/3.4/library/functions.html#bytes) представление Unicode-строки, закодированное в запрошенной *encoding*.

Параметр *errors* такой же, как параметр метода [`decode()`](https://python-all.ru/3.4/library/stdtypes.html#bytes.decode), но поддерживает несколько дополнительных обработчиков. Помимо `'strict'`, `'ignore'` и `'replace'` (который в этом случае вставляет вопросительный знак вместо некодируемого символа), есть также `'xmlcharrefreplace'` (вставляет XML-ссылку на символ) и `backslashreplace` (вставляет управляющую последовательность `\uNNNN`).

В следующем примере показаны различные результаты:

```python
>>> 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'
```

Низкоуровневые подпрограммы для регистрации и доступа к доступным кодировкам находятся в модуле [`codecs`](https://python-all.ru/3.4/library/codecs.html#module-codecs). Реализация новых кодировок также требует понимания модуля [`codecs`](https://python-all.ru/3.4/library/codecs.html#module-codecs). Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно слишком низкоуровневые, чтобы быть удобными, а написание новых кодировок – специализированная задача, поэтому этот модуль не будет рассматриваться в данном HOWTO.

### Литералы Unicode в исходном коде Python

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

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

Использование управляющих последовательностей для кодовых точек больше 127 допустимо в небольших количествах, но становится утомительным, если используется много символов с диакритическими знаками, как в программе с сообщениями на французском или другом языке, использующем такие знаки. Можно также собирать строки с помощью встроенной функции [`chr()`](https://python-all.ru/3.4/library/functions.html#chr), но это ещё более кропотливо.

В идеале хотелось бы иметь возможность записывать литералы в естественной кодировке вашего языка. Тогда можно было бы редактировать исходный код Python в любимом редакторе, который будет отображать символы с диакритическими знаками естественно, а при выполнении программы будут использоваться правильные символы.

По умолчанию Python поддерживает запись исходного кода в UTF-8, но можно использовать почти любую кодировку, если объявить используемую кодировку. Это делается путём включения специального комментария в первую или вторую строку исходного файла:

```python
#!/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**](https://python-all.ru/3.4/howto/unicode.html) для получения дополнительной информации.

### Свойства Unicode

Спецификация Unicode включает базу данных информации о кодовых точках. Для каждой определённой кодовой точки информация включает имя символа, его категорию, числовое значение, если применимо (Unicode содержит символы, представляющие римские цифры и дроби, такие как одна треть и четыре пятых). Существуют также свойства, связанные с использованием кодовой точки в двунаправленном тексте и другие свойства, относящиеся к отображению.

Следующая программа выводит некоторую информацию о нескольких символах и печатает числовое значение одного конкретного символа:

```python
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]))
```

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

```text
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
```

Коды категорий – это сокращения, описывающие природу символа. Они сгруппированы в категории, такие как «Letter», «Number», «Punctuation» или «Symbol», которые в свою очередь разбиты на подкатегории. Взяв коды из приведённого выше вывода, `'Ll'` означает «Letter, lowercase» (буква, строчная), `'No'` – «Number, other» (число, прочее), `'Mn'` – «Mark, nonspacing» (знак, некомбинирующий), а `'So'` – «Symbol, other» (символ, прочее). Список кодов категорий см. в [разделе General Category Values документации Unicode Character Database](https://python-all.ru/3.4/howto/unicode.html).

### Регулярные выражения Unicode

Регулярные выражения, поддерживаемые модулем [`re`](https://python-all.ru/3.4/library/re.html#module-re), могут быть заданы как в виде байтов, так и в виде строк. Некоторые специальные последовательности символов, такие как `\d` и `\w`, имеют разные значения в зависимости от того, задан ли шаблон как байты или как строка. Например, `\d` будет соответствовать символам `[0-9]` в байтах, но в строках будет соответствовать любому символу из категории `'Nd'`.

Строка в этом примере содержит число 57, записанное как тайскими, так и арабскими цифрами:

```python
import re
p = re.compile('\d+')

s = "Over \u0e55\u0e57 57 flavours"
m = p.search(s)
print(repr(m.group()))
```

При выполнении `\d+` будет соответствовать тайским цифрам и выведет их. Если передать флаг [`re.ASCII`](https://python-all.ru/3.4/library/re.html#re.ASCII) в [`compile()`](https://python-all.ru/3.4/library/re.html#re.compile), то `\d+` будет соответствовать подстроке «57».

Аналогично, `\w` соответствует широкому спектру символов Unicode, но только `[a-zA-Z0-9_]` в байтах или если указан [`re.ASCII`](https://python-all.ru/3.4/library/re.html#re.ASCII), а `\s` будет соответствовать либо пробельным символам Unicode, либо `[ \t\n\r\f\v]`.

### Ссылки

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

- [Обработка текстовых файлов в Python 3](https://python-all.ru/3.4/howto/unicode.html), автор Ник Коглан.
- [Pragmatic Unicode](https://python-all.ru/3.4/howto/unicode.html) – презентация Неда Бэтчелдера на PyCon 2012.

Тип [`str`](https://python-all.ru/3.4/library/stdtypes.html#str) описан в справочнике по библиотеке Python в разделе [*Text Sequence Type – str*](https://python-all.ru/3.4/library/stdtypes.html#textseq).

Документация модуля [`unicodedata`](https://python-all.ru/3.4/library/unicodedata.html#module-unicodedata).

Документация модуля [`codecs`](https://python-all.ru/3.4/library/codecs.html#module-codecs).

Марк-Андре Лембург представил [доклад под названием «Python and Unicode» (слайды в PDF)](https://python-all.ru/3.4/howto/unicode.html) на 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()`](https://python-all.ru/3.4/library/functions.html#open) может возвращать файлоподобный объект, который предполагает, что содержимое файла находится в указанной кодировке, и принимает параметры Unicode для таких методов, как [`read()`](https://python-all.ru/3.4/library/io.html#io.TextIOBase.read) и [`write()`](https://python-all.ru/3.4/library/io.html#io.TextIOBase.write). Это работает благодаря параметрам [`open()`](https://python-all.ru/3.4/library/functions.html#open) *encoding* и *errors*, которые интерпретируются так же, как в [`str.encode()`](https://python-all.ru/3.4/library/stdtypes.html#str.encode) и [`bytes.decode()`](https://python-all.ru/3.4/library/stdtypes.html#bytes.decode).

Чтение Unicode из файла, таким образом, просто:

```python
with open('unicode.txt', encoding='utf-8') as f:
    for line in f:
        print(repr(line))
```

Также можно открывать файлы в режиме обновления, позволяющем как чтение, так и запись:

```python
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' для little-endian и big-endian соответственно, которые задают конкретный порядок байтов и не пропускают BOM.

В некоторых областях также принято использовать «BOM» в начале файлов, закодированных в UTF-8; это название вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Метка просто объявляет, что файл закодирован в UTF-8. Используйте кодировку 'utf-8-sig', чтобы автоматически пропускать метку, если она присутствует, при чтении таких файлов.

### Имена файлов в Unicode

Большинство распространённых сегодня операционных систем поддерживают имена файлов, содержащие произвольные символы Юникода. Обычно это реализуется преобразованием строки Юникода в некоторую кодировку, зависящую от системы. Например, Mac OS X использует UTF-8, а Windows – настраиваемую кодировку; в Windows Python использует имя “mbcs” для обозначения текущей настроенной кодировки. В Unix-системах кодировка файловой системы существует только при установке переменных окружения `LANG` или `LC_CTYPE`; в противном случае кодировкой по умолчанию является UTF-8.

Функция [`sys.getfilesystemencoding()`](https://python-all.ru/3.4/library/sys.html#sys.getfilesystemencoding) возвращает кодировку, используемую в текущей системе, на случай, если понадобится выполнить кодирование вручную, но обычно в этом нет особой необходимости. При открытии файла для чтения или записи можно просто передать строку Юникода в качестве имени файла, и она будет автоматически преобразована в нужную кодировку.

```python
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
    f.write('blah\n')
```

Функции из модуля [`os`](https://python-all.ru/3.4/library/os.html#module-os), такие как [`os.stat()`](https://python-all.ru/3.4/library/os.html#os.stat), также принимают имена файлов в Юникоде.

Функция [`os.listdir()`](https://python-all.ru/3.4/library/os.html#os.listdir) возвращает имена файлов и порождает вопрос: должна ли она возвращать имена файлов в Юникоде или байты, содержащие их закодированные версии? [`os.listdir()`](https://python-all.ru/3.4/library/os.html#os.listdir) делает и то, и другое, в зависимости от того, был ли передан путь к каталогу в виде байтов или строки Юникода. Если передать путь как строку Юникода, имена файлов будут декодированы с использованием кодировки файловой системы, и будет возвращён список строк Юникода; если передать путь как байты, имена файлов будут возвращены в виде байтов. Например, если предположить, что кодировка файловой системы по умолчанию – UTF-8, выполнение следующей программы:

```python
fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()

import os
print(os.listdir(b'.'))
print(os.listdir('.'))
```

выдаст следующий результат:

```python
amk:~$ python t.py
[b'filename\xe4\x94\x80abc', ...]
['filename\u4500abc', ...]
```

Первый список содержит имена файлов в кодировке UTF-8, а второй список – версии в Unicode.

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

### Советы по написанию программ с поддержкой Unicode

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

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

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

Если попытаться написать функции обработки, которые принимают как строки Юникода, так и байтовые строки, программа будет уязвима для ошибок в местах, где используются строки разных типов. Автоматического кодирования или декодирования не происходит: например, при попытке выполнить `str + bytes` будет возбуждено исключение [`TypeError`](https://python-all.ru/3.4/library/exceptions.html#TypeError).

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

#### Преобразование между кодировками файлов

Класс [`StreamRecoder`](https://python-all.ru/3.4/library/codecs.html#codecs.StreamRecoder) может прозрачно преобразовывать между кодировками, принимая поток, возвращающий данные в кодировке #1, и ведя себя как поток, возвращающий данные в кодировке #2.

Например, если имеется входной файл *f* в кодировке Latin-1, его можно обернуть с помощью [`StreamRecoder`](https://python-all.ru/3.4/library/codecs.html#codecs.StreamRecoder), чтобы получать байты, закодированные в UTF-8:

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

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

#### Файлы в неизвестной кодировке

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

```python
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` при кодировании данных и их записи обратно.

### Ссылки

Одна из секций доклада [Mastering Python 3 Input/Output](https://python-all.ru/3.4/howto/unicode.html) на PyCon 2010 (Дэвид Бизли) посвящена обработке текста и работе с двоичными данными.

В [слайдах PDF презентации Марка-Андре Лембурга «Writing Unicode-aware Applications in Python»](https://python-all.ru/3.4/howto/unicode.html) рассматриваются вопросы кодировок символов, а также интернационализация и локализация приложений. Эти слайды охватывают только Python 2.x.

[The Guts of Unicode in Python](https://python-all.ru/3.4/howto/unicode.html) – доклад Бенджамина Петерсона на PyCon 2013, в котором обсуждается внутреннее представление Unicode в Python 3.3.

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

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

Благодарности следующим людям, которые заметили ошибки или предложили улучшения для этой статьи: Éric Araujo, Nicholas Bastin, Nick Coghlan, Marius Gedminas, Kent Johnson, Ken Krugler, Marc-André Lemburg, Martin von Löwis, Terry J. Reedy, Chad Whitacre.
