unicode.md
1> **Источник:** https://python-all.ru/3.12/howto/unicode.html2>3> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.45---67# Руководство по Unicode89**Версия:**10111.121213В данном руководстве рассматривается поддержка Python спецификации Unicode для представления текстовых данных, а также объясняются различные проблемы, с которыми часто сталкиваются при работе с Unicode.1415## Введение в Unicode1617### Определения1819Современным программам необходимо уметь работать с самыми разными символами. Приложения часто интернационализируются для вывода сообщений и результатов на различных языках, выбираемых пользователем; одна и та же программа может выводить сообщение об ошибке на английском, французском, японском, иврите или русском. Веб-контент может быть написан на любом из этих языков, а также может содержать различные эмодзи. Строковый тип Python использует стандарт Unicode для представления символов, что позволяет программам на Python работать со всеми этими различными возможными символами.2021Unicode ([https://www.unicode.org/](https://python-all.ru/3.12/howto/unicode.html)) – это спецификация, цель которой – перечислить все символы, используемые в человеческих языках, и присвоить каждому символу уникальный код. Спецификации Unicode постоянно пересматриваются и обновляются для добавления новых языков и символов.2223**Символ** – это минимально возможная составляющая текста. «A», «B», «C» и т.д. – все это разные символы. То же самое относится к «È» и «Í». Символы различаются в зависимости от языка или контекста, о котором идет речь. Например, существует символ «Римская единица» (Roman Numeral One) – «Ⅰ», который отличается от заглавной буквы «I». Обычно они выглядят одинаково, но это два разных символа с разными значениями.2425Стандарт Unicode описывает, как символы представляются с помощью **кодовых точек**. Значение кодовой точки – это целое число в диапазоне от 0 до 0x10FFFF (около 1,1 миллиона значений; [фактически назначенное количество](https://python-all.ru/3.12/howto/unicode.html) меньше). В стандарте и в этом документе кодовая точка записывается в виде `U+265E` для обозначения символа со значением `0x265e` (9 822 в десятичной системе).2627Стандарт Unicode содержит множество таблиц с перечислением символов и соответствующих им кодовых точек:2829```text300061 'a'; LATIN SMALL LETTER A310062 'b'; LATIN SMALL LETTER B320063 'c'; LATIN SMALL LETTER C33...34007B '{'; LEFT CURLY BRACKET35...362167 'Ⅷ'; ROMAN NUMERAL EIGHT372168 'Ⅸ'; ROMAN NUMERAL NINE38...39265E '♞'; BLACK CHESS KNIGHT40265F '♟'; BLACK CHESS PAWN41...421F600 '😀'; GRINNING FACE431F609 '😉'; WINKING FACE44...45```4647Строго говоря, из этих определений следует, что бессмысленно говорить «это символ `U+265E`». `U+265E` – это кодовая точка, которая представляет некоторый конкретный символ; в данном случае она представляет символ «BLACK CHESS KNIGHT», «♞». В неформальном контексте это различие между кодовыми точками и символами иногда забывается.4849Символ отображается на экране или на бумаге с помощью набора графических элементов, который называется **глифом**. Глиф для заглавной A, например, представляет собой две диагональные и одну горизонтальную черту, хотя точные детали зависят от используемого шрифта. Большинству кода на Python не нужно беспокоиться о глифах; определение правильного глифа для отображения обычно является задачей GUI-инструментария или рендерера шрифтов терминала.5051### Кодировки5253Резюмируя предыдущий раздел: строка Unicode – это последовательность кодовых точек, которые являются числами от 0 до `0x10FFFF` (1 114 111 в десятичной системе). Эту последовательность кодовых точек необходимо представить в памяти в виде набора **кодовых единиц**, а **кодовые единицы** затем отображаются на 8-битные байты. Правила преобразования строки Unicode в последовательность байтов называются **кодировкой символов** или просто **кодировкой**.5455Первое, что приходит на ум, – использовать 32-битные целые числа в качестве кодовой единицы и применяемое в CPU представление этих чисел. В таком представлении строка «Python» может выглядеть так:5657```text58 P y t h o n590x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 0060 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 2361```6263Такое представление интуитивно понятно, но его использование порождает ряд проблем.64651. Оно не является переносимым: разные процессоры по-разному упорядочивают байты.662. Оно крайне неэкономно расходует место. В большинстве текстов кодовые точки в основном меньше 127 или 255, поэтому значительная часть места занята `0x00` байтами. Приведённая выше строка занимает 24 байта, тогда как в ASCII-представлении достаточно 6 байт. Увеличение расхода оперативной памяти не так критично (настольные компьютеры имеют гигабайты RAM, а строки редко бывают очень большими), однако четырёхкратное расширение при использовании дискового пространства и сетевой пропускной способности уже неприемлемо.673. Оно несовместимо с существующими C-функциями, такими как `strlen()`, поэтому пришлось бы использовать новое семейство функций для работы с широкими строками.6869Поэтому данная кодировка используется редко; вместо неё выбирают другие, более эффективные и удобные, например UTF-8.7071UTF-8 – одна из самых распространённых кодировок; Python часто использует её по умолчанию. Аббревиатура UTF расшифровывается как «Unicode Transformation Format» (формат преобразования Юникода), а цифра 8 означает, что в этой кодировке используются 8-битные значения. (Существуют также UTF-16 и UTF-32, но они применяются реже.) UTF-8 руководствуется следующими правилами:72731. Если кодовая точка \< 128, она представляется соответствующим значением байта.742. Если кодовая точка \>= 128, она преобразуется в последовательность из двух, трёх или четырёх байт, причём каждый байт находится в диапазоне от 128 до 255.7576UTF-8 обладает несколькими удобными свойствами:77781. Она может обрабатывать любую кодовую точку Юникода.792. Строка Юникода преобразуется в последовательность байт, содержащую встроенные нулевые байты только там, где они представляют нулевой символ (U+0000). Это означает, что строки UTF-8 могут обрабатываться C-функциями, такими как `strcpy()`, и передаваться по протоколам, которые не допускают нулевые байты ни для чего, кроме маркеров конца строки.803. Любая строка ASCII также является корректным текстом UTF-8.814. UTF-8 довольно компактна; большинство часто используемых символов можно представить одним или двумя байтами.825. Если байты повреждены или потеряны, можно определить начало следующей UTF-8-кодовой точки и восстановить синхронизацию. Кроме того, маловероятно, что случайные 8-битные данные будут выглядеть как корректный UTF-8.836. UTF-8 – это байт-ориентированная кодировка. Она задаёт, что каждый символ представляется определённой последовательностью из одного или нескольких байт. Это позволяет избежать проблем с порядком байт, возникающих в целочисленных и слово-ориентированных кодировках, таких как UTF-16 и UTF-32, где последовательность байт меняется в зависимости от аппаратной платформы, на которой строка была закодирована.8485### Ссылки8687The [Unicode Consortium site](https://python-all.ru/3.12/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.12/howto/unicode.html) of the origin and development of Unicode is also available on the site.8889On the Computerphile Youtube channel, Tom Scott briefly [discusses the history of Unicode and UTF-8](https://python-all.ru/3.12/howto/unicode.html) (9 minutes 36 seconds).9091To help understand the standard, Jukka Korpela has written [an introductory guide](https://python-all.ru/3.12/howto/unicode.html) to reading the Unicode character tables.9293Another [good introductory article](https://python-all.ru/3.12/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.9495Wikipedia entries are often helpful; see the entries for “[character encoding](https://python-all.ru/3.12/howto/unicode.html)” and [UTF-8](https://python-all.ru/3.12/howto/unicode.html), for example.9697## Поддержка Юникода в Python9899Теперь, когда вы освоили основы Юникода, давайте рассмотрим возможности Python по работе с ним.100101### Тип строки102103Начиная с Python 3.0 строковый тип [`str`](https://python-all.ru/3.12/library/stdtypes.html#str) содержит символы Юникода – это означает, что любая строка, созданная с помощью `"unicode rocks!"`, `'unicode rocks!'` или синтаксиса тройных кавычек, хранится в Юникоде.104105Кодировка исходного кода Python по умолчанию – UTF-8, поэтому можно просто включить символ Юникода в строковый литерал:106107```python108try:109 with open('/tmp/input.txt', 'r') as f:110 ...111except OSError:112 # Сообщение об ошибке 'File not found'.113 print("Fichier non trouvé")114```115116Попутное замечание: Python 3 также поддерживает использование символов Юникода в идентификаторах:117118```python119répertoire = "/tmp/records.log"120with open(répertoire, "w") as f:121 f.write("test\n")122```123124Если вы не можете ввести нужный символ в редакторе или по какой-то причине хотите сохранить исходный код только в ASCII, можно использовать escape-последовательности в строковых литералах. (В зависимости от системы вместо u-escape может отображаться сама заглавная дельта.)125126```python127>>> "\N{GREEK CAPITAL LETTER DELTA}" # Использование имени символа128'\u0394'129>>> "\u0394" # Использование 16-битного шестнадцатеричного значения130'\u0394'131>>> "\U00000394" # Использование 32-битного шестнадцатеричного значения132'\u0394'133```134135Кроме того, строку можно создать методом [`decode()`](https://python-all.ru/3.12/library/stdtypes.html#bytes.decode) объекта [`bytes`](https://python-all.ru/3.12/library/stdtypes.html#bytes). Этот метод принимает аргумент *encoding* (кодировка), например `UTF-8`, и опционально аргумент *errors* (реакция на ошибки).136137Аргумент *errors* определяет реакцию, когда входную строку невозможно преобразовать согласно правилам кодировки. Допустимые значения этого аргумента: `'strict'` (вызывает исключение [`UnicodeDecodeError`](https://python-all.ru/3.12/library/exceptions.html#UnicodeDecodeError)), `'replace'` (использует `U+FFFD`, `REPLACEMENT CHARACTER`), `'ignore'` (просто исключает символ из результата Юникода) или `'backslashreplace'` (вставляет escape-последовательность `\xNN`). Следующие примеры показывают различия:138139```python140>>> b'\x80abc'.decode("utf-8", "strict")141Traceback (most recent call last):142 ...143UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:144 invalid start byte145>>> b'\x80abc'.decode("utf-8", "replace")146'\ufffdabc'147>>> b'\x80abc'.decode("utf-8", "backslashreplace")148'\\x80abc'149>>> b'\x80abc'.decode("utf-8", "ignore")150'abc'151```152153Encodings are specified as strings containing the encoding’s name. Python comes with roughly 100 different encodings; see the Python Library Reference at [Standard Encodings](https://python-all.ru/3.12/library/codecs.html#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.154155Односимвольные строки Юникода можно также создать с помощью встроенной функции [`chr()`](https://python-all.ru/3.12/library/functions.html#chr). Она принимает целое число и возвращает строку Юникода длиной 1, содержащую соответствующую кодовую точку. Обратная операция – встроенная функция [`ord()`](https://python-all.ru/3.12/library/functions.html#ord), которая принимает односимвольную строку Юникода и возвращает значение кодовой точки:156157```python158>>> chr(57344)159'\ue000'160>>> ord('\ue000')16157344162```163164### Преобразование в байты165166Противоположным методом для [`bytes.decode()`](https://python-all.ru/3.12/library/stdtypes.html#bytes.decode) является [`str.encode()`](https://python-all.ru/3.12/library/stdtypes.html#str.encode), который возвращает [`bytes`](https://python-all.ru/3.12/library/stdtypes.html#bytes) представление строки Unicode, закодированное в требуемой *кодировке*.167168Параметр *errors* такой же, как и параметр метода [`decode()`](https://python-all.ru/3.12/library/stdtypes.html#bytes.decode), но поддерживает несколько дополнительных обработчиков. Помимо `'strict'`, `'ignore'` и `'replace'` (который в данном случае вставляет знак вопроса вместо некодируемого символа), есть также `'xmlcharrefreplace'` (вставляет ссылку на символ XML), `backslashreplace` (вставляет управляющую последовательность `\uNNNN`) и `namereplace` (вставляет управляющую последовательность `\N{...}`).169170В следующем примере показаны различные результаты:171172```python173>>> u = chr(40960) + 'abcd' + chr(1972)174>>> u.encode('utf-8')175b'\xea\x80\x80abcd\xde\xb4'176>>> u.encode('ascii')177Traceback (most recent call last):178 ...179UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in180 position 0: ordinal not in range(128)181>>> u.encode('ascii', 'ignore')182b'abcd'183>>> u.encode('ascii', 'replace')184b'?abcd?'185>>> u.encode('ascii', 'xmlcharrefreplace')186b'ꀀabcd޴'187>>> u.encode('ascii', 'backslashreplace')188b'\\ua000abcd\\u07b4'189>>> u.encode('ascii', 'namereplace')190b'\\N{YI SYLLABLE IT}abcd\\u07b4'191```192193Низкоуровневые функции для регистрации и доступа к доступным кодировкам находятся в модуле [`codecs`](https://python-all.ru/3.12/library/codecs.html#module-codecs). Реализация новых кодировок также требует понимания модуля [`codecs`](https://python-all.ru/3.12/library/codecs.html#module-codecs). Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно слишком низкоуровневые для комфортной работы, а написание новых кодировок является специализированной задачей, поэтому этот модуль не будет рассматриваться в данном HOWTO.194195### Литералы Unicode в исходном коде Python196197В исходном коде Python конкретные кодовые точки Unicode можно записать с помощью управляющей последовательности `\u`, за которой следуют четыре шестнадцатеричные цифры, задающие кодовую точку. Управляющая последовательность `\U` аналогична, но ожидает восемь шестнадцатеричных цифр, а не четыре:198199```python200>>> s = "a\xac\u1234\u20ac\U00008000"201... # ^^^^ двузначная шестнадцатеричная escape-последовательность202... # ^^^^^^ четырёхзначная Unicode escape-последовательность203... # ^^^^^^^^^^ восьмизначная Unicode escape-последовательность204>>> [ord(c) for c in s]205[97, 172, 4660, 8364, 32768]206```207208Использование управляющих последовательностей для кодовых точек больше 127 вполне допустимо в небольших количествах, но становится утомительным, если используется много символов с диакритическими знаками, как, например, в программе с сообщениями на французском или другом языке, использующем диакритику. Также можно собирать строки с помощью встроенной функции [`chr()`](https://python-all.ru/3.12/library/functions.html#chr), но это ещё более трудоёмко.209210В идеале хотелось бы иметь возможность записывать литералы в естественной кодировке вашего языка. Тогда можно было бы редактировать исходный код Python в любимом редакторе, который будет отображать символы с диакритическими знаками естественно, а при выполнении программы будут использоваться правильные символы.211212По умолчанию Python поддерживает запись исходного кода в UTF-8, но можно использовать почти любую кодировку, если объявить используемую кодировку. Это делается путём включения специального комментария в первую или вторую строку исходного файла:213214```python215#!/usr/bin/env python216# -*- coding: latin-1 -*-217218u = 'abcdé'219print(ord(u[-1]))220```221222Синтаксис вдохновлён нотацией Emacs для указания локальных переменных файла. Emacs поддерживает множество различных переменных, но Python поддерживает только ‘coding’. Символы `-*-` указывают Emacs, что комментарий специальный; они не имеют значения для Python, но являются соглашением. Python ищет `coding: name` или `coding=name` в комментарии.223224Если такой комментарий не включён, по умолчанию будет использоваться кодировка UTF-8, как уже упоминалось. См. также [**PEP 263**](https://python-all.ru/3.12/howto/unicode.html) для получения дополнительной информации.225226### Свойства Unicode227228Спецификация Unicode включает базу данных информации о кодовых точках. Для каждой определённой кодовой точки информация включает имя символа, его категорию, числовое значение, если применимо (для символов, представляющих числовые концепции, такие как римские цифры, дроби, например одна треть и четыре пятых, и т.д.). Существуют также свойства, связанные с отображением, например, как использовать кодовую точку в двунаправленном тексте.229230Следующая программа выводит некоторую информацию о нескольких символах и печатает числовое значение одного конкретного символа:231232```python233import unicodedata234235u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)236237for i, c in enumerate(u):238 print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")239 print(unicodedata.name(c))240241# Получить числовое значение второго символа242print(unicodedata.numeric(u[1]))243```244245При запуске выводится:246247```text2480 00e9 Ll LATIN SMALL LETTER E WITH ACUTE2491 0bf2 No TAMIL NUMBER ONE THOUSAND2502 0f84 Mn TIBETAN MARK HALANTA2513 1770 Lo TAGBANWA LETTER SA2524 33af So SQUARE RAD OVER S SQUARED2531000.0254```255256Коды категорий – это сокращения, описывающие природу символа. Они группируются в такие категории, как «Буква», «Число», «Знак препинания» или «Символ», которые в свою очередь делятся на подкатегории. Взяв коды из приведённого выше вывода, `'Ll'` означает «Буква, строчная», `'No'` означает «Число, прочее», `'Mn'` – «Знак, некомбинируемый», а `'So'` – «Символ, прочее». См. [раздел «Значения общих категорий» документации по базе данных символов Unicode](https://python-all.ru/3.12/howto/unicode.html) для списка кодов категорий.257258### Сравнение строк259260Unicode вносит некоторые сложности в сравнение строк, потому что один и тот же набор символов может быть представлен разными последовательностями кодовых точек. Например, буква «ê» может быть представлена как одна кодовая точка U+00EA, или как U+0065 U+0302, что является кодовой точкой для «e», за которой следует кодовая точка для «КОМБИНИРУЕМОГО ЦИРКУМФЛЕКСА». Эти представления будут давать одинаковый вывод при печати, но одна строка имеет длину 1, а другая – длину 2.261262Одним инструментом для сравнения без учёта регистра является строковый метод [`casefold()`](https://python-all.ru/3.12/library/stdtypes.html#str.casefold), который преобразует строку в форму без учёта регистра по алгоритму, описанному в стандарте Unicode. Этот алгоритм имеет специальную обработку для таких символов, как немецкая буква «ß» (кодовая точка U+00DF), которая превращается в пару строчных букв «ss».263264```python265>>> street = 'Gürzenichstraße'266>>> street.casefold()267'gürzenichstrasse'268```269270Вторым инструментом является функция [`normalize()`](https://python-all.ru/3.12/library/unicodedata.html#unicodedata.normalize) модуля [`unicodedata`](https://python-all.ru/3.12/library/unicodedata.html#module-unicodedata), которая преобразует строки в одну из нескольких нормальных форм, где буквы, за которыми следует комбинируемый символ, заменяются на одиночные символы. [`normalize()`](https://python-all.ru/3.12/library/unicodedata.html#unicodedata.normalize) можно использовать для сравнения строк, которое не будет ложно сообщать о неравенстве, если две строки по-разному используют комбинируемые символы:271272```python273import unicodedata274275def compare_strs(s1, s2):276 def NFD(s):277 return unicodedata.normalize('NFD', s)278279 return NFD(s1) == NFD(s2)280281single_char = 'ê'282multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'283print('length of first string=', len(single_char))284print('length of second string=', len(multiple_chars))285print(compare_strs(single_char, multiple_chars))286```287288При запуске выводится:289290```console291$ python compare-strs.py292length of first string= 1293length of second string= 2294True295```296297Первым аргументом функции [`normalize()`](https://python-all.ru/3.12/library/unicodedata.html#unicodedata.normalize) является строка, задающая желаемую форму нормализации, которая может быть одной из «NFC», «NFKC», «NFD» и «NFKD».298299Стандарт Unicode также определяет, как выполнять сравнение без учёта регистра:300301```python302import unicodedata303304def compare_caseless(s1, s2):305 def NFD(s):306 return unicodedata.normalize('NFD', s)307308 return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())309310# Пример использования311single_char = 'ê'312multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'313314print(compare_caseless(single_char, multiple_chars))315```316317Это выведет `True`. (Почему `NFD()` вызывается дважды? Потому что существует несколько символов, из-за которых [`casefold()`](https://python-all.ru/3.12/library/stdtypes.html#str.casefold) возвращает ненормализованную строку, поэтому результат необходимо снова нормализовать. См. раздел 3.13 стандарта Unicode для обсуждения и примера.)318319### Регулярные выражения Unicode320321Регулярные выражения, поддерживаемые модулем [`re`](https://python-all.ru/3.12/library/re.html#module-re), могут быть представлены как в виде байтов, так и строк. Некоторые специальные последовательности символов, такие как `\d` и `\w`, имеют разные значения в зависимости от того, задан ли шаблон как байты или строка. Например, `\d` будет соответствовать символам `[0-9]` в байтах, но в строках будет соответствовать любому символу из категории `'Nd'`.322323Строка в этом примере содержит число 57, записанное как тайскими, так и арабскими цифрами:324325```python326import re327p = re.compile(r'\d+')328329s = "Over \u0e55\u0e57 57 flavours"330m = p.search(s)331print(repr(m.group()))332```333334При выполнении `\d+` будет соответствовать тайским цифрам и выведет их на печать. Если передать флаг [`re.ASCII`](https://python-all.ru/3.12/library/re.html#re.ASCII) функции [`compile()`](https://python-all.ru/3.12/library/re.html#re.compile), то `\d+` будет соответствовать подстроке «57».335336Аналогично, `\w` соответствует широкому спектру символов Unicode, но только `[a-zA-Z0-9_]` в байтах или если указан [`re.ASCII`](https://python-all.ru/3.12/library/re.html#re.ASCII), а `\s` будет соответствовать либо пробельным символам Unicode, либо `[ \t\n\r\f\v]`.337338### Ссылки339340Некоторые хорошие альтернативные обсуждения поддержки Unicode в Python:341342- [Обработка текстовых файлов в Python 3](https://python-all.ru/3.12/howto/unicode.html), автор Ник Коглан.343- [Pragmatic Unicode](https://python-all.ru/3.12/howto/unicode.html) – презентация Неда Бэтчелдера на PyCon 2012.344345Тип [`str`](https://python-all.ru/3.12/library/stdtypes.html#str) описан в справочнике по библиотеке Python по адресу [Text Sequence Type – str](https://python-all.ru/3.12/library/stdtypes.html#textseq).346347Документация модуля [`unicodedata`](https://python-all.ru/3.12/library/unicodedata.html#module-unicodedata).348349Документация модуля [`codecs`](https://python-all.ru/3.12/library/codecs.html#module-codecs).350351Марк-Андре Лембург [выступил с презентацией «Python and Unicode» (слайды в PDF)](https://python-all.ru/3.12/howto/unicode.html) на EuroPython 2002. Слайды дают отличный обзор архитектуры поддержки Unicode в Python 2 (где тип строки Unicode называется `unicode`, а литералы начинаются с `u`).352353## Чтение и запись данных Unicode354355После написания кода, работающего с данными Unicode, следующая проблема – ввод/вывод. Как передавать строки Unicode в программу и как преобразовывать Unicode в форму, подходящую для хранения или передачи?356357Возможно, вам может не понадобиться ничего делать в зависимости от источников ввода и мест назначения вывода; следует проверить, поддерживают ли библиотеки, используемые в вашем приложении, Unicode нативно. Например, XML-парсеры часто возвращают данные в Unicode. Многие реляционные базы данных также поддерживают столбцы со значением Unicode и могут возвращать значения Unicode из SQL-запросов.358359Данные Unicode обычно преобразуются в определённую кодировку перед записью на диск или отправкой через сокет. Можно сделать всю работу самостоятельно: открыть файл, прочитать из него 8-битный объект bytes и преобразовать байты с помощью `bytes.decode(encoding)`. Однако ручной подход не рекомендуется.360361Одна из проблем – многобайтовая природа кодировок; один символ Unicode может быть представлен несколькими байтами. Если читать файл произвольными фрагментами (например, по 1024 или 4096 байт), необходимо написать код обработки ошибок для ситуации, когда в конце фрагмента считывается только часть байтов, кодирующих один символ Unicode. Одним из решений было бы прочитать весь файл в память и затем выполнить декодирование, но это не позволяет работать с очень большими файлами; если нужно прочитать файл размером 2 ГБ, потребуется 2 ГБ ОЗУ. (На самом деле больше, поскольку хотя бы на мгновение в памяти должны находиться и закодированная строка, и её Unicode-версия.)362363Решением было бы использование низкоуровневого интерфейса декодирования для обработки неполных кодовых последовательностей. Работа по его реализации уже сделана за вас: встроенная функция [`open()`](https://python-all.ru/3.12/library/functions.html#open) может вернуть файлоподобный объект, который предполагает, что содержимое файла находится в указанной кодировке, и принимает параметры Unicode для методов, таких как [`read()`](https://python-all.ru/3.12/library/io.html#io.TextIOBase.read) и [`write()`](https://python-all.ru/3.12/library/io.html#io.TextIOBase.write). Это работает через параметры [`open()`](https://python-all.ru/3.12/library/functions.html#open) *encoding* и *errors*, которые интерпретируются так же, как в [`str.encode()`](https://python-all.ru/3.12/library/stdtypes.html#str.encode) и [`bytes.decode()`](https://python-all.ru/3.12/library/stdtypes.html#bytes.decode).364365Чтение Unicode из файла, таким образом, просто:366367```python368with open('unicode.txt', encoding='utf-8') as f:369 for line in f:370 print(repr(line))371```372373Также можно открывать файлы в режиме обновления, позволяющем как чтение, так и запись:374375```python376with open('test', encoding='utf-8', mode='w+') as f:377 f.write('\u4500 blah blah blah\n')378 f.seek(0)379 print(repr(f.readline()[:1]))380```381382Символ Unicode `U+FEFF` используется как метка порядка байтов (BOM) и часто записывается как первый символ файла для облегчения автоматического определения порядка байтов файла. Некоторые кодировки, такие как UTF-16, ожидают наличие BOM в начале файла; при использовании такой кодировки BOM автоматически записывается как первый символ и молча отбрасывается при чтении файла. Существуют варианты этих кодировок, например, ‘utf-16-le’ и ‘utf-16-be’ для little-endian и big-endian кодировок, которые задают определённый порядок байтов и не пропускают BOM.383384В некоторых областях также принято использовать «BOM» в начале файлов, закодированных в UTF-8; это название вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Метка просто сообщает, что файл закодирован в UTF-8. Для чтения таких файлов используйте кодировку ‘utf-8-sig’, чтобы автоматически пропускать метку, если она присутствует.385386### Имена файлов в Unicode387388Большинство операционных систем, используемых сегодня, поддерживают имена файлов, содержащие произвольные символы Unicode. Обычно это реализуется преобразованием строки Unicode в некоторую кодировку, которая зависит от системы. В настоящее время Python сходится на использовании UTF-8: Python на MacOS использует UTF-8 уже несколько версий, а Python 3.6 перешёл на использование UTF-8 и в Windows. В Unix-системах [кодировка файловой системы](https://python-all.ru/3.12/glossary.html#term-filesystem-encoding-and-error-handler) будет существовать только в том случае, если заданы переменные окружения `LANG` или `LC_CTYPE`; если не заданы, то по умолчанию снова используется UTF-8.389390Функция [`sys.getfilesystemencoding()`](https://python-all.ru/3.12/library/sys.html#sys.getfilesystemencoding) возвращает кодировку для использования в текущей системе, на случай если потребуется выполнить кодирование вручную, но в этом нет особой необходимости. При открытии файла для чтения или записи обычно можно просто передать строку Unicode в качестве имени файла, и она будет автоматически преобразована в нужную кодировку:391392```python393filename = 'filename\u4500abc'394with open(filename, 'w') as f:395 f.write('blah\n')396```397398Функции модуля [`os`](https://python-all.ru/3.12/library/os.html#module-os), такие как [`os.stat()`](https://python-all.ru/3.12/library/os.html#os.stat), также принимают имена файлов Unicode.399400Функция [`os.listdir()`](https://python-all.ru/3.12/library/os.html#os.listdir) возвращает имена файлов, что вызывает вопрос: следует ли возвращать версию имён в Unicode или возвращать байты, содержащие закодированные версии? [`os.listdir()`](https://python-all.ru/3.12/library/os.html#os.listdir) может делать и то, и другое в зависимости от того, передан ли путь к каталогу как байты или как строка Unicode. Если передать строку Unicode в качестве пути, имена файлов будут декодированы с использованием кодировки файловой системы и будет возвращён список строк Unicode, тогда как передача байтового пути вернёт имена файлов в виде байтов. Например, если предположить, что [кодировка файловой системы](https://python-all.ru/3.12/glossary.html#term-filesystem-encoding-and-error-handler) по умолчанию – UTF-8, выполнение следующей программы:401402```python403fn = 'filename\u4500abc'404f = open(fn, 'w')405f.close()406407import os408print(os.listdir(b'.'))409print(os.listdir('.'))410```411412выдаст следующий результат:413414```console415$ python listdir-test.py416[b'filename\xe4\x94\x80abc', ...]417['filename\u4500abc', ...]418```419420Первый список содержит имена файлов в кодировке UTF-8, а второй список – версии в Unicode.421422Обратите внимание, что в большинстве случаев для этих API достаточно просто использовать Unicode. API для байтов следует применять только в системах, где могут встречаться не декодируемые имена файлов; сейчас это практически только Unix-системы.423424### Советы по написанию программ с поддержкой Unicode425426В этом разделе приводятся некоторые рекомендации по написанию программного обеспечения, работающего с Unicode.427428Самый важный совет:429430> Программное обеспечение должно работать внутри только со строками Unicode, декодируя входные данные как можно раньше и кодируя вывод только в конце.431432При попытке написать функции обработки, которые принимают как строки Unicode, так и байтовые строки, программа окажется уязвима для ошибок везде, где комбинируются два разных типа строк. Автоматического кодирования или декодирования не происходит: если выполнить, например, `str + bytes`, будет возбуждено [`TypeError`](https://python-all.ru/3.12/library/exceptions.html#TypeError).433434При использовании данных из веб-браузера или другого ненадёжного источника распространённый приём – проверка строки на недопустимые символы перед использованием в генерируемой командной строке или сохранением в базе данных. Если это делается, следует проверять декодированную строку, а не закодированные байтовые данные; некоторые кодировки могут обладать интересными свойствами, такими как отсутствие биективности или полной совместимости с ASCII. Это особенно важно, если входные данные также задают кодировку, так как злоумышленник может выбрать хитрый способ скрыть вредоносный текст в закодированном потоке байтов.435436#### Преобразование между кодировками файлов437438Класс [`StreamRecoder`](https://python-all.ru/3.12/library/codecs.html#codecs.StreamRecoder) может прозрачно преобразовывать между кодировками, принимая поток, возвращающий данные в кодировке №1, и ведя себя как поток, возвращающий данные в кодировке №2.439440Например, если есть входной файл *f* в кодировке Latin-1, его можно обернуть с помощью [`StreamRecoder`](https://python-all.ru/3.12/library/codecs.html#codecs.StreamRecoder), чтобы возвращать байты, закодированные в UTF-8:441442```python443new_f = codecs.StreamRecoder(f,444 # кодер/декодер: используется read() для кодирования результатов и445 # с помощью write() для декодирования своего ввода.446 codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),447448 # читатель/писатель: используется для чтения и записи в поток.449 codecs.getreader('latin-1'), codecs.getwriter('latin-1') )450```451452#### Файлы в неизвестной кодировке453454Что делать, если нужно внести изменения в файл, но кодировка файла неизвестна? Если известно, что кодировка совместима с ASCII и требуется только просмотреть или изменить части в ASCII, можно открыть файл с обработчиком ошибок `surrogateescape`:455456```python457with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:458 data = f.read()459460# внести изменения в строку 'data'461462with open(fname + '.new', 'w',463 encoding="ascii", errors="surrogateescape") as f:464 f.write(data)465```466467Обработчик ошибок `surrogateescape` декодирует все не-ASCII байты как кодовые точки в специальном диапазоне от U+DC80 до U+DCFF. Затем эти кодовые точки снова превращаются в те же байты, когда обработчик ошибок `surrogateescape` используется для кодирования данных и записи их обратно.468469### Ссылки470471Одна из секций доклада [Mastering Python 3 Input/Output](https://python-all.ru/3.12/howto/unicode.html) на PyCon 2010 (Дэвид Бизли) посвящена обработке текста и работе с двоичными данными.472473В [слайдах PDF презентации Марка-Андре Лембурга «Writing Unicode-aware Applications in Python»](https://python-all.ru/3.12/howto/unicode.html) рассматриваются вопросы кодировок символов, а также интернационализация и локализация приложений. Эти слайды охватывают только Python 2.x.474475[The Guts of Unicode in Python](https://python-all.ru/3.12/howto/unicode.html) – доклад Бенджамина Петерсона на PyCon 2013, в котором обсуждается внутреннее представление Unicode в Python 3.3.476477## Благодарности478479Первоначальный черновик этого документа написал Эндрю Кухлинг. Впоследствии он был доработан Александром Белопольским, Георгом Брандлем, Эндрю Кухлингом и Эцио Мелотти.480481Благодарности следующим людям, которые указали на ошибки или предложили улучшения: Эрик Араужо, Николас Бастин, Ник Коглан, Мариус Гедминас, Кент Джонсон, Кен Круглер, Марк-Андре Лембург, Мартин фон Лёвис, Терри Дж. Риди, Сергей Сторчака, Эрик Сан, Чад Уиттакр, Грэм Уайдман.482