Руководство по Unicode
- Версия:
-
1.12
В этом руководстве рассматривается поддержка Python спецификации Unicode для представления текстовых данных и объясняются различные проблемы, с которыми часто сталкиваются при работе с Unicode.
Введение в Unicode
Определения
Современные программы должны уметь обрабатывать самые разные символы. Приложения часто интернационализируют, чтобы отображать сообщения и вывод на разных языках, выбираемых пользователем; одной и той же программе может потребоваться вывести сообщение об ошибке на английском, французском, японском, иврите или русском языке. Веб-контент может быть написан на любом из этих языков, а также содержать различные символы эмодзи. Для представления символов в строковом типе Python используется стандарт Unicode, благодаря чему программы на Python могут работать со всеми этими различными символами.
Unicode (https://www.unicode.org/) — это спецификация, цель которой — перечислить все символы, используемые в человеческих языках, и присвоить каждому символу уникальный код. Спецификации Unicode постоянно пересматриваются и обновляются, чтобы добавлять новые языки и символы.
Символ — это наименьший возможный компонент текста. «A», «B», «C» и т. д. — все это разные символы. Так же как «È» и «Í». Символы различаются в зависимости от языка или контекста, о котором идет речь. Например, существует символ «римская цифра один» — «Ⅰ», который отличается от заглавной буквы «I». Обычно они выглядят одинаково, но это два разных символа с разными значениями.
Стандарт Unicode описывает представление символов с помощью кодовых точек. Значение кодовой точки — это целое число в диапазоне от 0 до 0x10FFFF (примерно 1,1 миллиона значений; фактически присвоено меньше). В стандарте и в этом документе кодовая точка записывается в виде U+265E и обозначает символ со значением 0x265e (9 822 в десятичной системе счисления).
Стандарт Unicode содержит множество таблиц со списками символов и соответствующих им кодовых точек:
0061 'a'; LATIN SMALL LETTER A
0062 'b'; LATIN SMALL LETTER B
0063 'c'; LATIN SMALL LETTER C
...
007B '{'; LEFT CURLY BRACKET
...
2167 'Ⅷ'; ROMAN NUMERAL EIGHT
2168 'Ⅸ'; ROMAN NUMERAL NINE
...
265E '♞'; BLACK CHESS KNIGHT
265F '♟'; BLACK CHESS PAWN
...
1F600 '😀'; GRINNING FACE
1F609 '😉'; WINKING FACE
...
Строго говоря, из этих определений следует, что выражение «это символ U+265E» не имеет смысла. U+265E — это кодовая точка, представляющая некоторый конкретный символ; в данном случае она представляет символ «ЧЁРНЫЙ КОНЬ» — «♞». В неформальном контексте различие между кодовыми точками и символами иногда упускают из виду.
На экране или на бумаге символ представлен набором графических элементов, который называется глифом. Например, глиф заглавной A состоит из двух диагональных штрихов и одного горизонтального, хотя точные детали зависят от используемого шрифта. Большинству программ на Python не нужно заботиться о глифах; выбор подходящего глифа для отображения обычно является задачей графического интерфейса или механизма отображения шрифтов терминала.
Кодировки
Подведем итог предыдущему разделу: строка Unicode — это последовательность кодовых точек, то есть чисел от 0 до 0x10FFFF (1 114 111 в десятичной системе счисления). Эту последовательность кодовых точек необходимо представить в памяти набором кодовых единиц, которые затем преобразуются в 8-битные байты. Правила преобразования строки Unicode в последовательность байтов называются кодировкой символов, или просто кодировкой.
Первый вариант кодировки, который может прийти в голову, — использовать 32-битные целые числа в качестве кодовых единиц, а затем представлять их так, как это делает процессор. При таком представлении строка «Python» может выглядеть так:
P y t h o n 0x50 00 00 00 79 00 00 00 74 00 00 00 68 00 00 00 6f 00 00 00 6e 00 00 00 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
Такое представление просто, но его использование создает ряд проблем.
- Оно непереносимо: разные процессоры располагают байты в разном порядке.
- Оно крайне неэкономно расходует пространство. В большинстве текстов основная часть кодовых точек меньше 127 или меньше 255, поэтому много места занимают
0x00байта. Для приведенной выше строки требуется 24 байта, тогда как для представления ASCII нужно 6 байт. Увеличение расхода оперативной памяти не слишком важно (настольные компьютеры располагают гигабайтами оперативной памяти, а строки обычно не бывают такими большими), но увеличение в четыре раза расхода места на диске и пропускной способности сети неприемлемо. - Оно несовместимо с существующими функциями C, такими как
strlen(), поэтому пришлось бы использовать новое семейство функций для широких строк.
Поэтому эта кодировка используется нечасто, и вместо нее выбирают другие, более эффективные и удобные кодировки, например UTF-8.
UTF-8 — одна из наиболее распространенных кодировок, и Python часто использует ее по умолчанию. UTF означает «Unicode Transformation Format» (формат преобразования Unicode), а цифра 8 означает, что в кодировке используются 8-битные значения. (Существуют также кодировки UTF-16 и UTF-32, но они используются реже, чем UTF-8.) В UTF-8 применяются следующие правила:
- Если кодовая точка меньше 128, она представляется соответствующим значением байта.
- Если кодовая точка больше или равна 128, она преобразуется в последовательность из двух, трех или четырех байтов, каждый из которых имеет значение от 128 до 255.
UTF-8 обладает рядом удобных свойств:
- Она поддерживает любую кодовую точку Unicode.
- Строка Unicode преобразуется в последовательность байтов, содержащую нулевые байты только там, где они представляют нулевой символ (U+0000). Это означает, что строки UTF-8 можно обрабатывать функциями C, такими как
strcpy(), и передавать по протоколам, которые не могут обрабатывать нулевые байты иначе как в качестве маркеров конца строки. - Строка с текстом ASCII также является допустимым текстом UTF-8.
- UTF-8 достаточно компактна: большинство часто используемых символов представляются одним или двумя байтами.
- Если байты повреждены или потеряны, можно определить начало следующей кодовой точки UTF-8 и восстановить синхронизацию. Кроме того, маловероятно, что произвольные 8-битные данные будут похожи на корректный UTF-8.
- UTF-8 — байтовая кодировка. В ней каждый символ представлен определенной последовательностью из одного или нескольких байтов. Это позволяет избежать проблем с порядком байтов, которые могут возникать в кодировках, ориентированных на целые числа и машинные слова, таких как UTF-16 и UTF-32, где последовательность байтов зависит от оборудования, на котором была закодирована строка.
Ссылки
На сайте Консорциума Unicode размещены таблицы символов, глоссарий и версии спецификации Unicode в формате PDF. Будьте готовы к тому, что чтение будет непростым. На сайте также доступна хронология возникновения и развития Unicode.
Том Скотт кратко рассказывает об истории Unicode и UTF-8 на YouTube-канале Computerphile (9 минут 36 секунд).
Чтобы помочь разобраться в стандарте, Юкка Корпела написал вводное руководство по таблицам символов Unicode.
Еще одну хорошую вводную статью написал Джоэл Спольски. Если после этого введения вам все еще что-то непонятно, попробуйте прочитать эту альтернативную статью, прежде чем продолжить.
Статьи в Википедии часто бывают полезны; например, см. статьи о «кодировке символов» и UTF-8.
Поддержка Unicode в Python
Теперь, когда вы познакомились с основами Unicode, можно рассмотреть возможности Python для работы с Unicode.
Строковый тип
Начиная с Python 3.0, тип str в языке содержит символы Unicode. Это означает, что любая строка, созданная с помощью "unicode rocks!", 'unicode
rocks!' или синтаксиса тройных кавычек, хранится как Unicode.
Кодировка исходного кода Python по умолчанию — UTF-8, поэтому вы можете просто включить символ Unicode в строковый литерал:
try:
with open('/tmp/input.txt', 'r') as f:
...
except OSError:
# 'File not found' error message.
print("Fichier non trouvé")
Примечание: Python 3 также поддерживает использование символов Unicode в идентификаторах:
répertoire = "/tmp/records.log"
with open(répertoire, "w") as f:
f.write("test\n")
Если вы не можете ввести определенный символ в редакторе или по какой-либо причине хотите оставить исходный код только в ASCII, можно также использовать escape-последовательности в строковых литералах. (В зависимости от системы вместо escape-последовательности u вы можете увидеть сам глиф заглавной дельты.)
>>> "\N{GREEK CAPITAL LETTER DELTA}" # Using the character name
'\u0394'
>>> "\u0394" # Using a 16-bit hex value
'\u0394'
>>> "\U00000394" # Using a 32-bit hex value
'\u0394'
Кроме того, строку можно создать с помощью метода decode() типа bytes. Этот метод принимает аргумент encoding, например UTF-8, и необязательный аргумент errors.
Аргумент errors задает реакцию на случай, когда входную строку нельзя преобразовать согласно правилам кодировки. Допустимые значения этого аргумента: 'strict' (вызывает исключение UnicodeDecodeError), 'replace' (использует U+FFFD, REPLACEMENT CHARACTER), 'ignore' (просто исключает символ из результата Unicode) или 'backslashreplace' (вставляет escape-последовательность \xNN). В следующих примерах показаны различия:
>>> b'\x80abc'.decode("utf-8", "strict")
Traceback (most recent call last):
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x80 in position 0:
invalid start byte
>>> b'\x80abc'.decode("utf-8", "replace")
'\ufffdabc'
>>> b'\x80abc'.decode("utf-8", "backslashreplace")
'\\x80abc'
>>> b'\x80abc'.decode("utf-8", "ignore")
'abc'
Кодировки задаются строками с названием кодировки. Python поддерживает примерно 100 различных кодировок; их список см. в справочнике по библиотеке Python в разделе Стандартные кодировки. Некоторые кодировки имеют несколько названий; например, 'latin-1', 'iso_8859_1' и '8859 — синонимы одной и той же кодировки.
Строки Unicode из одного символа также можно создать с помощью встроенной функции chr(), которая принимает целые числа и возвращает строку Unicode длиной 1 с соответствующей кодовой точкой. Обратная операция выполняется встроенной функцией ord(): она принимает строку Unicode из одного символа и возвращает значение кодовой точки:
>>> chr(57344)
'\ue000'
>>> ord('\ue000')
57344
Преобразование в байты
Противоположностью метода bytes.decode() является str.encode(), который возвращает представление строки Unicode в виде bytes, закодированное с использованием запрошенной кодировки.
Параметр errors совпадает с параметром метода decode(), но поддерживает еще несколько обработчиков. Помимо 'strict', 'ignore' и 'replace' (в этом случае вместо символа, который невозможно закодировать, вставляется вопросительный знак), доступны также 'xmlcharrefreplace' (вставляет ссылку на символ XML), backslashreplace (вставляет escape-последовательность \uNNNN) и namereplace (вставляет escape-последовательность \N{...}).
В следующем примере показаны различные результаты:
>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
...
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'ꀀabcd޴'
>>> u.encode('ascii', 'backslashreplace')
b'\\ua000abcd\\u07b4'
>>> u.encode('ascii', 'namereplace')
b'\\N{YI SYLLABLE IT}abcd\\u07b4'
Низкоуровневые процедуры для регистрации и доступа к доступным кодировкам находятся в модуле codecs. Для реализации новых кодировок также необходимо разбираться в модуле codecs. Однако функции кодирования и декодирования, предоставляемые этим модулем, обычно имеют слишком низкий уровень для удобной работы, а создание новых кодировок — узкоспециализированная задача, поэтому в этом руководстве модуль рассматриваться не будет.
Литералы Unicode в исходном коде Python
В исходном коде Python конкретные кодовые точки Unicode можно записывать с помощью escape-последовательности \u, за которой следуют четыре шестнадцатеричные цифры, задающие кодовую точку. Escape-последовательность \U похожа на нее, но ожидает восемь, а не четыре шестнадцатеричные цифры:
>>> s = "a\xac\u1234\u20ac\U00008000" ... # ^^^^ two-digit hex escape ... # ^^^^^^ four-digit Unicode escape ... # ^^^^^^^^^^ eight-digit Unicode escape >>> [ord(c) for c in s] [97, 172, 4660, 8364, 32768]
Использовать escape-последовательности для кодовых точек со значением больше 127 вполне допустимо, если их немного, но это становится неудобным, когда нужно использовать много символов с диакритическими знаками, например в программе с сообщениями на французском или другом языке, где используются такие знаки. Строки также можно составлять с помощью встроенной функции chr(), но это еще более утомительно.
В идеале хотелось бы записывать литералы в естественной для языка кодировке. Тогда можно было бы редактировать исходный код Python в любимом редакторе, который естественным образом отображал бы символы с диакритическими знаками, и во время выполнения использовались бы нужные символы.
Python по умолчанию поддерживает запись исходного кода в UTF-8, но можно использовать почти любую кодировку, если указать, какая именно используется. Для этого в первую или вторую строку исходного файла добавляют специальный комментарий:
#!/usr/bin/env python # -*- coding: latin-1 -*- u = 'abcdé' print(ord(u[-1]))
Синтаксис основан на обозначении Emacs для задания переменных, локальных для файла. Emacs поддерживает множество различных переменных, но Python поддерживает только «coding». Символы -*- указывают Emacs на то, что комментарий является специальным; для Python они не имеют значения и используются лишь по соглашению. Python ищет в комментарии coding: name или coding=name.
Если такой комментарий не добавлен, как уже упоминалось, по умолчанию используется кодировка UTF-8. Дополнительную информацию см. также в PEP 263.
Свойства Unicode
Спецификация Unicode включает базу данных со сведениями о кодовых точках. Для каждой определенной кодовой точки в ней указаны название символа, его категория и, если применимо, числовое значение (для символов, представляющих числовые понятия, например римских цифр и дробей, таких как одна треть и четыре пятых, и т. д.). Также имеются свойства, связанные с отображением, например сведения об использовании кодовой точки в двунаправленном тексте.
Следующая программа выводит некоторые сведения о нескольких символах и числовое значение одного конкретного символа:
import unicodedata
u = chr(233) + chr(0x0bf2) + chr(3972) + chr(6000) + chr(13231)
for i, c in enumerate(u):
print(i, '%04x' % ord(c), unicodedata.category(c), end=" ")
print(unicodedata.name(c))
# Get numeric value of second character
print(unicodedata.numeric(u[1]))
При запуске она выводит:
0 00e9 Ll LATIN SMALL LETTER E WITH ACUTE 1 0bf2 No TAMIL NUMBER ONE THOUSAND 2 0f84 Mn TIBETAN MARK HALANTA 3 1770 Lo TAGBANWA LETTER SA 4 33af So SQUARE RAD OVER S SQUARED 1000.0
Коды категорий — это сокращения, описывающие свойства символа. Они объединены в категории, такие как «Буква», «Число», «Пунктуация» или «Символ», которые, в свою очередь, разделены на подкатегории. Взяв коды из приведенного выше вывода, получим: 'Ll' означает «Буква, строчная», 'No' — «Число, прочее», 'Mn' — «Знак, не имеющий ширины», а 'So' — «Символ, прочее». Список кодов категорий см. в разделе «Значения общей категории» документации базы данных символов Unicode.
Сравнение строк
Unicode усложняет сравнение строк, поскольку один и тот же набор символов может быть представлен различными последовательностями кодовых точек. Например, букву «ê» можно представить одной кодовой точкой U+00EA или последовательностью U+0065 U+0302, то есть кодовой точкой для «e», за которой следует кодовая точка для «COMBINING CIRCUMFLEX ACCENT». При выводе они выглядят одинаково, но одна строка имеет длину 1, а другая — длину 2.
Для сравнения без учета регистра можно использовать строковый метод casefold(), который преобразует строку в форму без учета регистра согласно алгоритму, описанному в стандарте Unicode. Этот алгоритм предусматривает особую обработку таких символов, как немецкая буква «ß» (кодовая точка U+00DF), которая преобразуется в пару строчных букв «ss».
>>> street = 'Gürzenichstraße' >>> street.casefold() 'gürzenichstrasse'
Еще один инструмент — функция normalize() модуля unicodedata, преобразующая строки в одну из нескольких нормальных форм, в которых буквы, за которыми следует комбинируемый символ, заменяются одиночными символами. normalize() можно использовать для сравнения строк, чтобы различное использование комбинируемых символов не приводило к ложному выводу о неравенстве строк:
import unicodedata
def compare_strs(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(s1) == NFD(s2)
single_char = 'ê'
multiple_chars = '\N{LATIN SMALL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print('length of first string=', len(single_char))
print('length of second string=', len(multiple_chars))
print(compare_strs(single_char, multiple_chars))
При запуске выводится:
$ python compare-strs.py length of first string= 1 length of second string= 2 True
Первый аргумент функции normalize() — это строка, задающая нужную форму нормализации. Ею может быть одна из следующих форм: «NFC», «NFKC», «NFD» и «NFKD».
Стандарт Unicode также определяет способ сравнения без учета регистра:
import unicodedata
def compare_caseless(s1, s2):
def NFD(s):
return unicodedata.normalize('NFD', s)
return NFD(NFD(s1).casefold()) == NFD(NFD(s2).casefold())
# Example usage
single_char = 'ê'
multiple_chars = '\N{LATIN CAPITAL LETTER E}\N{COMBINING CIRCUMFLEX ACCENT}'
print(compare_caseless(single_char, multiple_chars))
Будет выведено True. (Почему NFD() вызывается дважды? Потому что для некоторых символов результат casefold() не нормализован, поэтому результат нужно нормализовать еще раз. Обсуждение и пример см. в разделе 3.13 стандарта Unicode.)
Регулярные выражения Unicode
Регулярные выражения, поддерживаемые модулем re, могут задаваться как байтами, так и строками. Некоторые специальные последовательности символов, например \d и \w, имеют разное значение в зависимости от того, задан шаблон байтами или строкой. Например, \d будет соответствовать символам [0-9] в байтах, а в строках — любому символу категории 'Nd'.
В этой строке число 57 записано и тайскими, и арабскими цифрами:
import re p = re.compile(r'\d+') s = "Over \u0e55\u0e57 57 flavours" m = p.search(s) print(repr(m.group()))
При выполнении \d+ найдет тайские цифры и выведет их. Если передать флаг re.ASCII функции compile(), \d+ вместо этого найдет подстроку «57».
Аналогично, \w соответствует широкому набору символов Unicode, но в байтах или при передаче re.ASCII — только [a-zA-Z0-9_], а \s соответствует пробельным символам Unicode или [ \t\n\r\f\v].
Ссылки
Вот несколько хороших альтернативных материалов о поддержке Unicode в Python:
- Обработка текстовых файлов в Python 3, автор — Ник Коглан.
- Практический Unicode — доклад Неда Батчелдера на PyCon 2012.
Описание типа str приведено в справочнике по библиотеке Python в разделе Текстовые последовательности — str.
Документация модуля unicodedata.
Документация модуля codecs.
Марк-Андре Лембург выступил с докладом «Python и Unicode» (слайды в формате PDF) на EuroPython 2002. Слайды дают превосходный обзор устройства возможностей Unicode в Python 2 (где строковый тип Unicode называется unicode, а литералы начинаются с u).
Чтение и запись данных Unicode
После того как вы написали код для работы с данными Unicode, возникает следующая задача — ввод-вывод. Как передать строки Unicode в программу и преобразовать Unicode в форму, пригодную для хранения или передачи?
Возможно, вам вообще ничего не понадобится делать — это зависит от источников входных данных и получателей выходных; проверьте, поддерживают ли используемые в приложении библиотеки Unicode изначально. Например, парсеры XML часто возвращают данные Unicode. Многие реляционные базы данных также поддерживают столбцы со значениями Unicode и могут возвращать значения Unicode из SQL-запросов.
Перед записью на диск или отправкой через сокет данные Unicode обычно преобразуют в определённую кодировку. Всю работу можно выполнить самостоятельно: открыть файл, прочитать из него объект байтов с 8-битными значениями и преобразовать байты с помощью bytes.decode(encoding). Однако такой подход не рекомендуется.
Одна из проблем — многобайтовая природа кодировок: один символ Unicode может быть представлен несколькими байтами. Если нужно читать файл произвольными блоками (скажем, по 1024 или 4096 байт), потребуется написать код обработки ошибок, который учтёт ситуацию, когда в конце блока прочитана лишь часть байтов, кодирующих один символ Unicode. Одно из решений — прочитать весь файл в память, а затем выполнить декодирование, но тогда нельзя будет работать с очень большими файлами: чтобы прочитать файл размером 2 ГиБ, потребуется 2 ГиБ оперативной памяти. (На самом деле больше, поскольку хотя бы на мгновение в памяти должны будут находиться и закодированная строка, и её версия Unicode.)
Решением будет использование низкоуровневого интерфейса декодирования для обработки неполных последовательностей кодирования. Работа по его реализации уже выполнена за вас: встроенная функция open() может возвращать файловоподобный объект, который предполагает, что содержимое файла имеет указанную кодировку, и принимает параметры Unicode для таких методов, как read() и write(). Это работает благодаря параметрам encoding и errors функции open(), которые интерпретируются так же, как параметры соответствующих методов str.encode() и bytes.decode().
Поэтому читать Unicode из файла просто:
with open('unicode.txt', encoding='utf-8') as f:
for line in f:
print(repr(line))
Файлы также можно открывать в режиме обновления, который позволяет и читать, и записывать данные:
with open('test', encoding='utf-8', mode='w+') as f:
f.write('\u4500 blah blah blah\n')
f.seek(0)
print(repr(f.readline()[:1]))
Символ Unicode U+FEFF используется как маркер порядка байтов (BOM) и часто записывается первым символом файла, чтобы помочь автоматически определить порядок байтов в файле. Некоторые кодировки, например UTF-16, предполагают наличие BOM в начале файла; при использовании такой кодировки BOM будет автоматически записан первым символом и незаметно пропущен при чтении файла. Существуют варианты этих кодировок, такие как «utf-16-le» и «utf-16-be» для кодировок с порядком байтов от младшего к старшему и от старшего к младшему соответственно, которые задают определённый порядок байтов и не пропускают BOM.
В некоторых случаях принято также использовать «BOM» в начале файлов в кодировке UTF-8; это название вводит в заблуждение, поскольку UTF-8 не зависит от порядка байтов. Этот маркер лишь указывает, что файл закодирован в UTF-8. Для чтения таких файлов используйте кодек «utf-8-sig», который автоматически пропустит маркер, если он есть.
Имена файлов Unicode
Большинство распространённых сегодня операционных систем поддерживают имена файлов, содержащие произвольные символы Unicode. Обычно это реализовано с помощью преобразования строки Unicode в кодировку, которая зависит от системы. Сейчас Python переходит на UTF-8: в MacOS Python использует UTF-8 уже несколько версий, а начиная с Python 3.6 UTF-8 используется и в Windows. В Unix-системах кодировка файловой системы будет определяться только переменными окружения LANG или LC_CTYPE; если они не заданы, кодировкой по умолчанию также будет UTF-8.
Функция sys.getfilesystemencoding() возвращает кодировку, используемую в вашей системе, если вы хотите выполнить кодирование вручную, но обычно в этом нет особого смысла. При открытии файла для чтения или записи обычно достаточно указать имя файла в виде строки Unicode — оно будет автоматически преобразовано в нужную кодировку:
filename = 'filename\u4500abc'
with open(filename, 'w') as f:
f.write('blah\n')
Функции модуля os, например os.stat(), также принимают имена файлов Unicode.
Функция os.listdir() возвращает имена файлов, что вызывает вопрос: должна ли она возвращать имена файлов в формате Unicode или байты, содержащие закодированные версии? os.listdir() может делать и то и другое — в зависимости от того, передали ли вы путь к каталогу в виде байтов или строки Unicode. Если передать путь в виде строки Unicode, имена файлов будут декодированы с помощью кодировки файловой системы и будет возвращён список строк Unicode; если передать путь в виде байтов, имена файлов будут возвращены как байты. Например, если предположить, что кодировка файловой системы по умолчанию — UTF-8, выполнение следующей программы:
fn = 'filename\u4500abc'
f = open(fn, 'w')
f.close()
import os
print(os.listdir(b'.'))
print(os.listdir('.'))
даст следующий результат:
$ python listdir-test.py [b'filename\xe4\x94\x80abc', ...] ['filename\u4500abc', ...]
Первый список содержит имена файлов в кодировке UTF-8, а второй — их версии Unicode.
Обратите внимание: в большинстве случаев в этих API можно просто использовать Unicode. API для работы с байтами следует использовать только в системах, где могут встречаться имена файлов, которые нельзя декодировать; сейчас это почти исключительно Unix-системы.
Советы по написанию программ с поддержкой Unicode
В этом разделе приведены рекомендации по написанию программного обеспечения, работающего с Unicode.
Самая важная рекомендация:
Программное обеспечение должно работать внутри системы только со строками Unicode, как можно раньше декодируя входные данные и кодируя выходные только в самом конце.
Если попытаться написать функции обработки, принимающие как строки Unicode, так и байтовые строки, программа станет подвержена ошибкам везде, где смешиваются эти два разных типа строк. Автоматического кодирования или декодирования не происходит: если, например, выполнить str + bytes, будет вызвано исключение TypeError.
При работе с данными из веб-браузера или другого ненадёжного источника обычно проверяют строку на наличие недопустимых символов, прежде чем использовать её в создаваемой командной строке или сохранять в базе данных. Если вы поступаете так, проверяйте декодированную строку, а не закодированные байтовые данные: некоторые кодировки могут обладать интересными свойствами, например не быть биективными или полностью совместимыми с ASCII. Это особенно важно, если входные данные также указывают кодировку, поскольку тогда злоумышленник может выбрать хитрый способ скрыть вредоносный текст в закодированном потоке байтов.
Преобразование между кодировками файлов
Класс StreamRecoder может прозрачно преобразовывать данные между кодировками: он принимает поток, возвращающий данные в кодировке № 1, и ведёт себя как поток, возвращающий данные в кодировке № 2.
Например, если у вас есть входной файл f в кодировке Latin-1, его можно обернуть в StreamRecoder, чтобы получать байты в кодировке UTF-8:
new_f = codecs.StreamRecoder(f,
# en/decoder: used by read() to encode its results and
# by write() to decode its input.
codecs.getencoder('utf-8'), codecs.getdecoder('utf-8'),
# reader/writer: used to read and write to the stream.
codecs.getreader('latin-1'), codecs.getwriter('latin-1') )
Файлы с неизвестной кодировкой
Что делать, если нужно изменить файл, но его кодировка неизвестна? Если вы знаете, что кодировка совместима с ASCII, и хотите просмотреть или изменить только части ASCII, можно открыть файл с обработчиком ошибок surrogateescape:
with open(fname, 'r', encoding="ascii", errors="surrogateescape") as f:
data = f.read()
# make changes to the string 'data'
with open(fname + '.new', 'w',
encoding="ascii", errors="surrogateescape") as f:
f.write(data)
Обработчик ошибок surrogateescape декодирует все байты, не относящиеся к ASCII, как кодовые точки в специальном диапазоне от U+DC80 до U+DCFF. При кодировании данных и их последующей записи эти кодовые точки преобразуются обратно в те же байты, если использовать обработчик ошибок surrogateescape.
Ссылки
В одном из разделов доклада Освоение ввода-вывода в Python 3, представленного Дэвидом Бизли на PyCon 2010, рассматриваются обработка текста и работа с двоичными данными.
В слайдах в формате PDF к докладу Марка-Андре Ленбурга «Написание приложений с поддержкой Unicode на Python» рассматриваются вопросы кодировок символов, а также интернационализации и локализации приложений. В этих слайдах рассматривается только Python 2.x.
Внутреннее устройство Unicode в Python — доклад Бенджамина Петерсона на PyCon 2013, посвящённый внутреннему представлению Unicode в Python 3.3.
Благодарности
Первый вариант этого документа написал Эндрю Кучлинг. Позже его дорабатывали Александр Белопольский, Георг Брандл, Эндрю Кучлинг и Эцио Мелотти.
Благодарим следующих людей, которые указали на ошибки или предложили улучшения для этой статьи: Эрик Арахо, Николас Бастин, Ник Коглан, Мариус Гедминас, Кент Джонсон, Кен Круглер, Марк-Андре Ленбург, Мартин фон Лёвиc, Терри Дж. Риди, Сергиу Сторчака, Эрик Сан, Чад Уитакр, Грэм Уайдеман.
© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/unicode.html