Spec-Zone.ru › Python 3.13

Руководство по 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

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

  1. Оно непереносимо: разные процессоры располагают байты в разном порядке.
  2. Оно крайне неэкономно расходует место. В большинстве текстов большая часть кодовых точек меньше 127 или 255, поэтому много места занимают 0x00 байты. Указанная выше строка занимает 24 байта вместо 6 байтов, необходимых для представления в ASCII. Увеличение расхода оперативной памяти не так критично (настольные компьютеры располагают гигабайтами ОЗУ, а строки обычно не бывают такими большими), но увеличение расхода места на диске и пропускной способности сети в 4 раза неприемлемо.
  3. Оно несовместимо с существующими функциями C, такими как strlen(), поэтому пришлось бы использовать новое семейство функций для работы с широкими строками.

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

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

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

У UTF-8 есть несколько удобных свойств:

  1. Она поддерживает любую кодовую точку Unicode.
  2. Строка Unicode преобразуется в последовательность байтов, содержащую нулевые байты только в тех случаях, когда они представляют нулевой символ (U+0000). Это означает, что строки UTF-8 можно обрабатывать функциями C, такими как strcpy(), и передавать по протоколам, которые не поддерживают нулевые байты ни для чего, кроме обозначения конца строки.
  3. Строка текста в ASCII также является допустимым текстом UTF-8.
  4. UTF-8 довольно компактна: большинство часто используемых символов представляются одним или двумя байтами.
  5. Если байты повреждены или потеряны, можно определить начало следующей кодовой точки UTF-8 и восстановить синхронизацию. Кроме того, маловероятно, что случайные 8-битные данные будут выглядеть как корректная последовательность UTF-8.
  6. UTF-8 — это байтовая кодировка. В ней для каждого символа задаётся определённая последовательность из одного или нескольких байтов. Это позволяет избежать проблем с порядком байтов, которые могут возникать в кодировках, ориентированных на целые числа и машинные слова, например UTF-16 и UTF-32, где последовательность байтов зависит от оборудования, на котором была закодирована строка.

Ссылки

На сайте Консорциума 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, закодированное с использованием запрошенной encoding.

Параметр 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», за которой следует кодовая точка для «КОМБИНИРУЕМЫЙ АКЦЕНТ-КРЮК». При печати они дадут одинаковый результат, но одна строка будет иметь длину 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, но только [a-zA-Z0-9_] в байтах или при передаче флага re.ASCII, а \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 и преобразовать байты с помощью 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-системах кодировка файловой системы filesystem encoding будет отличаться от UTF-8 только в том случае, если заданы переменные окружения 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() может делать и то и другое — в зависимости от того, передали ли вы путь к каталогу в виде объекта bytes или строки Unicode. Если в качестве пути передана строка Unicode, имена файлов будут декодированы с помощью кодировки файловой системы и будет возвращён список строк Unicode; если передан путь в виде байтов, имена файлов будут возвращены как байты. Например, если предположить, что кодировкой файловой системы filesystem encoding по умолчанию является 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 для кодирования данных и их записи эти кодовые точки преобразуются обратно в те же байты.

Ссылки

В одном из разделов доклада Mastering Python 3 Input/Output, представленного Дэвидом Бизли на PyCon 2010, рассматриваются обработка текста и работа с двоичными данными.

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

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

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

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

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

© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/howto/unicode.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API