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

---

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

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

В этом руководстве рассматривается поддержка 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'.)

Некоторое время люди просто писали программы, которые не отображали диакритические знаки. Помню, как смотрел на программы на Apple \]\[ BASIC, опубликованные во франкоязычных изданиях в середине 1980-х, в которых были такие строки:

```python
PRINT "FICHIER EST COMPLETE."
PRINT "CARACTERE NON ACCEPTE."
```

Эти сообщения должны содержать диакритические знаки, и они выглядят неправильно для того, кто умеет читать по-французски.

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

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

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

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

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

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

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

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

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

```python
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. Эта последовательность должна быть представлена в виде набора байтов (то есть значений от 0 до 255) в памяти. Правила преобразования строки Unicode в последовательность байтов называются **кодировкой**.

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

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

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

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

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

1. Если кодовая точка меньше 128, она представляется соответствующим значением байта.
2. Если кодовая точка находится между 128 и 0x7ff, она преобразуется в два значения байтов от 128 до 255.
3. Кодовые точки больше 0x7ff преобразуются в последовательности из трёх или четырёх байтов, где каждый байт последовательности находится в диапазоне от 128 до 255.

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

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

### Ссылки

На сайте Unicode Consortium по адресу \<[http://www.unicode.org](https://python-all.ru/3.0/howto/unicode.html)\> есть таблицы символов, глоссарий и PDF-версии спецификации Unicode. Будьте готовы к сложному чтению. \<[http://www.unicode.org/history/](https://python-all.ru/3.0/howto/unicode.html)\> – это хронология происхождения и развития Unicode.

Чтобы помочь понять стандарт, Юкка Корпела написал вводное руководство по чтению таблиц символов Unicode, доступное по адресу \<[http://www.cs.tut.fi/~jkorpela/unicode/guide.html](https://python-all.ru/3.0/howto/unicode.html)\>.

Ещё две хорошие вводные статьи написали Джоэл Спольски \<[http://www.joelonsoftware.com/articles/Unicode.html](https://python-all.ru/3.0/howto/unicode.html)\> и Джейсон Орендорфф \<[http://www.jorendorff.com/articles/unicode/](https://python-all.ru/3.0/howto/unicode.html)\>. Если это введение не прояснило ситуацию, попробуйте прочитать одну из этих статей, прежде чем продолжать.

Статьи в Википедии часто полезны; см. статьи «character encoding» \<[http://en.wikipedia.org/wiki/Character\_encoding](https://python-all.ru/3.0/howto/unicode.html)\> и UTF-8 \<[http://en.wikipedia.org/wiki/UTF-8](https://python-all.ru/3.0/howto/unicode.html)\>, например.

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

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

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

Начиная с Python 3.0, в языке имеется тип `str`, содержащий символы Unicode, то есть любая строка, созданная с помощью `"unicode rocks!"`, `'unicode rocks!` или синтаксиса тройных кавычек, хранится в Unicode.

Чтобы вставить символ Unicode, не входящий в ASCII, например, любые буквы с диакритическими знаками, можно использовать escape-последовательности в строковых литералах:

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

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

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

```python
>>> b'\x80abc'.decode("utf-8", "strict")
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeDecodeError: 'utf8' codec can't decode byte 0x80 in position 0:
                    unexpected code byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'
```

Кодировки задаются строками, содержащими название кодировки. Python поставляется примерно со 100 различными кодировками; список см. в справочнике по библиотеке Python в разделе [*Standard Encodings*](https://python-all.ru/3.0/library/codecs.html#standard-encodings). Некоторые кодировки имеют несколько названий; например, 'latin-1', 'iso\_8859\_1' и '8859' – это синонимы одной и той же кодировки.

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

```python
>>> chr(40960)
'\ua000'
>>> ord('\ua000')
40960
```

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

Ещё один важный метод str – это `.encode([encoding], [errors='strict'])`, который возвращает `bytes`-представление строки Unicode, закодированной в запрошенной кодировке. Параметр `errors` совпадает с параметром метода `decode()`, с одним дополнительным вариантом; помимо ‘strict’, ‘ignore’ и ‘replace’ (который в данном случае вставляет вопросительный знак вместо некодируемого символа), можно также передать ‘xmlcharrefreplace’, который использует XML-ссылки на символы. Следующий пример показывает различные результаты:

```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):
  File "<stdin>", line 1, in ?
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;'
```

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

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

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

```python
>>> s = "a\xac\u1234\u20ac\U00008000"
          ^^^^ two-digit hex escape
               ^^^^^ four-digit Unicode escape
                          ^^^^^^^^^^ eight-digit Unicode escape
>>> for c in s:  print(ord(c), end=" ")
...
97 172 4660 8364 32768
```

Использование управляющих последовательностей для кодовых точек больше 127 допустимо в небольших количествах, но становится утомительным, если используется много символов с диакритическими знаками, как в программе с сообщениями на французском или другом языке, использующем такие знаки. Можно также собирать строки с помощью встроенной функции [`chr()`](https://python-all.ru/3.0/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, как уже упоминалось.

### Свойства 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]))
```

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

```python
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». См. \<[http://www.unicode.org/Public/UNIDATA/UCD.html#General\_Category\_Values](https://python-all.ru/3.0/howto/unicode.html)\> для получения списка кодов категорий.

### Ссылки

Тип `str` описан в справочнике по библиотеке Python в разделе [*Sequence Types – str, bytes, bytearray, list, tuple, range*](https://python-all.ru/3.0/library/stdtypes.html#typesseq).

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

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

Марк-Андре Лембург сделал доклад на EuroPython 2002 под названием «Python and Unicode». PDF-версия его слайдов доступна по адресу \<[http://downloads.egenix.com/python/Unicode-EPC2002-Talk.pdf](https://python-all.ru/3.0/howto/unicode.html)\> и представляет собой отличный обзор дизайна возможностей Python для работы с Unicode (основанный на Python 2, где тип строк Unicode называется `unicode`, а литералы начинаются с `u`).

## Чтение и запись данных Unicode

После написания кода, работающего с данными Unicode, следующая проблема – ввод/вывод. Как передавать строки Unicode в программу и как преобразовывать Unicode в форму, подходящую для хранения или передачи?

Возможно, вам может не понадобиться ничего делать в зависимости от источников ввода и мест назначения вывода; следует проверить, поддерживают ли библиотеки, используемые в вашем приложении, Unicode нативно. Например, XML-парсеры часто возвращают данные в Unicode. Многие реляционные базы данных также поддерживают столбцы со значением Unicode и могут возвращать значения Unicode из SQL-запросов.

Данные Unicode обычно преобразуются в определённую кодировку перед записью на диск или отправкой через сокет. Можно сделать всю работу самостоятельно: открыть файл, прочитать из него 8-битную строку байтов и преобразовать строку с помощью `str(bytes, encoding)`. Однако ручной подход не рекомендуется.

Одна из проблем – многобайтовая природа кодировок: один символ Unicode может быть представлен несколькими байтами. Если нужно читать файл фрагментами произвольного размера (скажем, 1 КБ или 4 КБ), придётся писать код обработки ошибок для случаев, когда на границе фрагмента оказалась только часть байтов, кодирующих один символ Unicode. Одно из решений – прочитать весь файл в память и затем декодировать, но это не позволяет работать с очень большими файлами: если нужно прочитать файл размером 2 ГБ, потребуется 2 ГБ оперативной памяти. (Даже больше, ведь в какой-то момент в памяти должны находиться и закодированная строка, и её версия в Unicode.)

Решение – использовать низкоуровневый интерфейс декодирования, чтобы выявить частичные кодовые последовательности. Эта работа уже проделана за вас: встроенная функция [`open()`](https://python-all.ru/3.0/library/functions.html#open) может вернуть объект, похожий на файл, который предполагает, что содержимое файла находится в заданной кодировке, и принимает параметры Unicode для таких методов, как `.read()` и `.write()`. Это работает через параметры [`open()`](https://python-all.ru/3.0/library/functions.html#open) – *encoding* и *errors*, которые интерпретируются так же, как соответствующие параметры методов `encode()` и `decode()` строковых объектов.

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

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

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

```python
f = open('test', encoding='utf-8', mode='w+')
f.write('\u4500 blah blah blah\n')
f.seek(0)
print(repr(f.readline()[:1]))
f.close()
```

Символ Unicode U+FEFF используется как маркер порядка байтов (BOM) и часто записывается первым в файл, чтобы упростить автоматическое определение порядка байтов. Некоторые кодировки, например UTF-16, ожидают наличие BOM в начале файла; при использовании такой кодировки BOM будет автоматически записан как первый символ и молча пропущен при чтении файла. Существуют варианты этих кодировок, такие как 'utf-16-le' и 'utf-16-be' для little-endian и big-endian, которые задают конкретный порядок байтов и не пропускают BOM.

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

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

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

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

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

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

Функция [`os.listdir()`](https://python-all.ru/3.0/library/os.html#os.listdir), возвращающая имена файлов, порождает вопрос: возвращать ли юникодную версию имён или байтовые строки с закодированными именами? [`os.listdir()`](https://python-all.ru/3.0/library/os.html#os.listdir) делает и то, и другое в зависимости от того, передали ли путь в виде байтовой строки или строки Unicode. Если передать путь как строку Unicode, имена файлов будут декодированы с использованием кодировки файловой системы и будет возвращён список строк Unicode. Если же передать путь как байтовую строку, будут возвращены байтовые версии имён файлов. Например, если предположить, что по умолчанию файловая система использует 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'.svn', b'filename\xe4\x94\x80abc', ...]
['.svn', 'filename\u4500abc', ...]
```

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

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

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

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

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

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

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

Такие проблемы легко упустить, если тестировать программу только на данных без диакритических знаков: всё будет казаться рабочим, но на самом деле в программе есть ошибка, которая ждёт первого же пользователя, попытавшегося использовать символы \> 127. Поэтому второй совет таков:

> Включайте в тестовые данные символы \> 127, а ещё лучше – символы \> 255.

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

Например, предположим, есть система управления контентом, которая принимает имя файла в Unicode, и нужно запретить пути с символом '/'. Можно написать такой код:

```python
def read_file(filename, encoding):
    if '/' in filename:
        raise ValueError("'/' not allowed in filenames")
    unicode_name = filename.decode(encoding)
    f = open(unicode_name, 'r')
    # ... вернуть содержимое файла ...
```

Однако если злоумышленник может задать кодировку `'base64'`, он может передать `'L2V0Yy9wYXNzd2Q='`, что является строкой в base64 от `'/etc/passwd'`, чтобы прочитать системный файл. Приведённый выше код ищет символы `'/'` в закодированной форме и пропускает опасный символ в результирующей декодированной форме.

### Ссылки

PDF-слайды презентации Марка-Андре Лембурга «Writing Unicode-aware Applications in Python» доступны по адресу \<[http://downloads.egenix.com/python/LSM2005-Developing-Unicode-aware-applications-in-Python.pdf](https://python-all.ru/3.0/howto/unicode.html)\> и обсуждают вопросы кодировок символов, а также интернационализацию и локализацию приложений.

## История изменений и благодарности

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

Версия 1.0: опубликована 5 августа 2005 года.

Версия 1.01: опубликована 7 августа 2005 года. Исправлены фактические ошибки и ошибки разметки; добавлено несколько ссылок.

Версия 1.02: опубликована 16 августа 2005 года. Исправлены фактические ошибки.

Версия 1.1: февраль–ноябрь 2008 года. Обновление документа с учётом изменений в Python 3.
