Документация Python неофициальный перевод
Содержание страницы

Руководство по UnicodeUnicode HOWTO

Версия:1.11

This HOWTO discusses Python 2.x’s support for Unicode, and explains various problems that people commonly encounter when trying to work with Unicode. (This HOWTO has not yet been updated to cover the 3.x versions of Python.)

Введение в UnicodeIntroduction to Unicode

История кодировок символовHistory of Character Codes

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

КодировкиEncodings

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

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

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

  1. Если кодовая точка < 128, каждый байт равен значению кодовой точки.
  2. Если кодовая точка равна 128 или больше, Unicode-строка не может быть представлена в этой кодировке. (В этом случае Python возбуждает исключение UnicodeEncodeError.)

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

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

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

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

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

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

СсылкиReferences

На сайте Unicode Consortium по адресу <http://www.unicode.org> есть таблицы символов, глоссарий и PDF-версии спецификации Unicode. Будьте готовы к сложному чтению. <http://www.unicode.org/history/> – это хронология происхождения и развития Unicode.

Чтобы помочь понять стандарт, Юкка Корпела написал вводное руководство по чтению таблиц символов Unicode, доступное по адресу <http://www.cs.tut.fi/~jkorpela/unicode/guide.html>.

Ещё одну хорошую вводную статью написал Джоэл Спольски <http://www.joelonsoftware.com/articles/Unicode.html>. Если это введение не прояснило для вас ситуацию, попробуйте прочитать эту альтернативную статью, прежде чем продолжать.

Статьи в Википедии часто полезны; см. статьи «character encoding» <http://en.wikipedia.org/wiki/Character_encoding> и UTF-8 <http://en.wikipedia.org/wiki/UTF-8>, например.

Поддержка Unicode в Python 2.xPython 2.x’s Unicode Support

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

Тип строкиThe String Type

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

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

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

Кроме того, можно создать строку с помощью метода decode() объекта bytes. Этот метод принимает кодировку, например UTF-8, и, опционально, аргумент errors.

Аргумент errors определяет реакцию, когда входную строку невозможно преобразовать в соответствии с правилами кодировки. Допустимые значения этого аргумента: ‘strict’ (вызвать исключение UnicodeDecodeError), ‘replace’ (использовать U+FFFD, ‘REPLACEMENT CHARACTER’) или ‘ignore’ (просто исключить символ из результата Unicode). Следующие примеры показывают различия:

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

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

Кодировки задаются строками, содержащими название кодировки. Python 3.2 поставляется примерно со 100 различными кодировками; см. Справочник по библиотеке Python по адресу Standard Encodings для списка. Некоторые кодировки имеют несколько названий; например, ‘latin-1’, ‘iso_8859_1’ и ‘8859’ – все синонимы одной и той же кодировки.

Односимвольные Unicode-строки также можно создать с помощью встроенной функции chr(), которая принимает целые числа и возвращает Unicode-строку длиной 1, содержащую соответствующую кодовую точку. Обратная операция – встроенная функция ord(), которая принимает односимвольную Unicode-строку и возвращает значение кодовой точки:

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

Преобразование в байтыConverting to Bytes

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

>>> u = chr(40960) + 'abcd' + chr(1972)
>>> u.encode('utf-8')
b'\xea\x80\x80abcd\xde\xb4'
>>> u.encode('ascii')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
UnicodeEncodeError: 'ascii' codec can't encode character '\ua000' in
                    position 0: ordinal not in range(128)
>>> u.encode('ascii', 'ignore')
b'abcd'
>>> u.encode('ascii', 'replace')
b'?abcd?'
>>> u.encode('ascii', 'xmlcharrefreplace')
b'&#40960;abcd&#1972;'

Низкоуровневые процедуры для регистрации и доступа к доступным кодировкам находятся в модуле codecs. Однако функции кодирования и декодирования, возвращаемые этим модулем, обычно слишком низкоуровневые для удобного использования, поэтому я не буду описывать модуль codecs здесь. Если вам нужно реализовать совершенно новую кодировку, вам придётся изучить интерфейсы модуля codecs, но реализация кодировок – специализированная задача, которая также не будет рассмотрена здесь. Обратитесь к документации Python, чтобы узнать больше об этом модуле.

Литералы Unicode в исходном коде PythonUnicode Literals in Python Source Code

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

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

Использование управляющих последовательностей для кодовых точек больше 127 допустимо в небольших количествах, но становится утомительным, если используется много символов с диакритическими знаками, как в программе с сообщениями на французском или другом языке, использующем такие знаки. Можно также собирать строки с помощью встроенной функции chr(), но это ещё более кропотливо.

В идеале хотелось бы иметь возможность записывать литералы в естественной кодировке вашего языка. Тогда можно было бы редактировать исходный код 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, как уже упоминалось.

Свойства UnicodeUnicode Properties

Спецификация 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))

# Получить числовое значение второго символа
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

Коды категорий – это аббревиатуры, описывающие природу символа. Они группируются в категории, такие как «Letter», «Number», «Punctuation» или «Symbol», которые, в свою очередь, делятся на подкатегории. Возьмём коды из приведённого выше вывода: 'Ll' означает «Letter, lowercase», 'No' означает «Number, other», 'Mn' – «Mark, nonspacing», а 'So' – «Symbol, other». См. <http://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values> для списка кодов категорий.

СсылкиReferences

Тип str описан в справочнике по библиотеке Python в разделе Sequence Types – str, bytes, bytearray, list, tuple, range.

Документация модуля unicodedata.

Документация модуля codecs.

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

Чтение и запись данных UnicodeReading and Writing Unicode Data

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

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

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

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

Решение – использовать низкоуровневый интерфейс декодирования, чтобы выявить частичные кодовые последовательности. Эта работа уже проделана за вас: встроенная функция open() может вернуть объект, похожий на файл, который предполагает, что содержимое файла находится в заданной кодировке, и принимает параметры Unicode для таких методов, как .read() и .write(). Это работает через параметры open()encoding и errors, которые интерпретируются так же, как соответствующие параметры методов encode() и decode() строковых объектов.

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

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

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

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

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

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

Имена файлов в UnicodeUnicode filenames

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

Функция sys.getfilesystemencoding() возвращает кодировку, используемую в текущей системе, на случай, если понадобится выполнить кодирование вручную, но обычно в этом нет особой необходимости. При открытии файла для чтения или записи можно просто передать строку Юникода в качестве имени файла, и она будет автоматически преобразована в нужную кодировку.

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

Функции из модуля os, такие как os.stat(), также принимают имена файлов в Юникоде.

Функция os.listdir(), возвращающая имена файлов, порождает вопрос: возвращать ли юникодную версию имён или байтовые строки с закодированными именами? 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('.'))

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

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

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

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

Советы по написанию программ с поддержкой UnicodeTips for Writing Unicode-aware Programs

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

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

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

Если попытаться написать функции обработки, принимающие как строки Unicode, так и байтовые строки, программа окажется уязвимой для ошибок везде, где комбинируются два разных типа строк. Автоматического кодирования или декодирования не происходит: например, выражение str + bytes вызывает TypeError.

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

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

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

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

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

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

СсылкиReferences

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

История изменений и благодарностиRevision History and Acknowledgements

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

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

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

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

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

Версия 1.11: опубликована 20 июня 2010 года. Отмечается, что Python 3 не рассматривается, и HOWTO охватывает только версию 2.x.