Как преобразовать строку Юникода в строковый объект в Питоне?

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

Истоки Юникода

Немного о Юникоде из Википедии.

Юникод — стандарт кодирования символов, включающий в себя знаки почти всех письменных языков мира. В настоящее время стандарт является преобладающим в Интернете.

Стандарт предложен в 1991 году некоммерческой организацией «Консорциум Юникода» (англ. Unicode Consortium, Unicode Inc). Применение этого стандарта позволяет закодировать очень большое число символов из разных систем письменности: в документах, закодированных по стандарту Юникод, могут соседствовать китайские иероглифы, математические символы, буквы греческого алфавита, латиницы и кириллицы, символы музыкальной нотной нотации, при этом становится ненужным переключение кодовых страниц.

В Юникоде существует несколько форм представления( от английского Unicode transformation format, UTF): UTF-8, UTF-16(UTF-16BE, UTF-16LE) и UTF-32(UTF-32BE, UTF-32LE).В потоке данных UTF-16 младший байт может записываться либо перед старшим (англ. UTF-16 little-endian, UTF-16LE), либо после старшего (англ. UTF-16 big-endian, UTF-16BE). Аналогично существует два варианта четырехбайтовой формы представления — UTF-32LE и UTF-32BE. Все их также называют кодировками.

В Microsoft Windows NT и основанных на ней системах в основном используется форма UTF-16LE. В UNIX-подобных операционных системах GNU/Linux, BSD и Mac OS X принята форма UTF-8 для файлов и UTF-32 или UTF-8 для обработки символов в оперативной памяти.

Часто мы получаем в виде входных данных строку символов Юникода, которая не читаема обычным пользователем, но имеет ряд преимуществ перед обычным текстом, например, меньше занимает места в памяти или требует меньше времени для обработки и дальнейшей передачи. В зависимости от дальнейших требований предъявляемых к строке Юникода или в зависимости от окружения(будь то операционная система или программное обеспечение) необходимо определиться с кодировкой, которую можно и нужно применить.

Кодировка UTF-8 сейчас является доминирующей в веб-пространстве. UTF-8, по сравнению с UTF-16, наибольший выигрыш в компактности дает для текстов на латинице, поскольку латинские буквы, цифры и наиболее распространённые знаки препинания кодируются в UTF-8 лишь одним байтом, и коды этих символов соответствуют их кодам в ASCII.

UTF-16 — кодировка, позволяющая записывать символы Юникода в диапазонах U+0000…U+D7FF и U+E000…U+10FFFF (общим количеством 1 112 064). При этом каждый символ записывается одним или двумя словами (суррогатная пара).

UTF-32 — способ представления Юникода, при котором каждый символ занимает ровно 4 байта. Главное преимущество UTF-32 перед кодировками переменной длины заключается в том, что символы Юникод в ней непосредственно индексируемы, поэтому найти символ по номеру его позиции в файле можно чрезвычайно быстро, и получение любого символа n-й позиции при этом является операцией, занимающей всегда одинаковое время. Это также делает замену символов в строках UTF-32 очень простой. Напротив, кодировки с переменной длиной требуют последовательного доступа к символу n-й позиции, что может быть очень затратной по времени операцией. Главный недостаток UTF-32 — это неэффективное использование пространства, так как для хранения любого символа используется четыре байта.

Формулировка проблемы

Предположим у нас есть строка в Юникоде и нам необходимо преобразовать её в строку в Питоне.

A = 'u041fu0440u0438u0432u0435u0442'

Убедимся в типе входных данных:

>>> type(A)
<class 'str'>

Метод 1. String

В Питоне 3 весь текст по умолчанию представлен строками Юникода, что также означает, что синтаксис u ‘<text>’ больше не используется.

Большинство интерпретаторов в Питоне поддерживают Юникод и при вызове функции print интерпретатор преобразует входную последовательность из unicode-escape символов в строку.

print(str(A))
# Привет

Проверять тип данных после применения метода string не имеет смысла

Метод 2. Repr()

Встроенная функция repr() возвращает строку, содержащую печатаемое формальное представление объекта.

print(repr(A))
# 'Привет'

Проверим тип данных:

print(type(repr(A)))
# <class 'str'>

Метод 3. Модуль Unicodedata, функция normalize

Функция normalize() модуля Unicodedata возвращает нормальную форму для строки Юникод. Допустимые значения для формы: NFC, NFKC, NFD и NFKD.

Стандарт Юникода определяет различные формы нормализации строки на основе определения канонической эквивалентности и эквивалентности совместимости. В Юникоде несколько символов могут быть выражены по-разному. Например, символ U + 00C7 (ЛАТИНСКАЯ ЗАГЛАВНАЯ С С СЕДИЛЬЕЙ) также может быть выражен как последовательность U + 0043 (ЛАТИНСКАЯ ЗАГЛАВНАЯ С) U + 0327 (КОМБИНИРУЕМАЯ СЕДИЛЬЯ).

Для каждого символа есть две нормальные формы: нормальная форма C и нормальная форма D. Нормальная форма D (NFD) также известна как каноническое разложение и переводит каждый символ в разложенную форму. Нормальная форма C (NFC) сначала применяет каноническую декомпозицию, затем снова создает предварительно объединенные символы.

В дополнение к этим двум формам существуют две дополнительные нормальные формы, основанные на эквивалентности совместимости. В Юникоде поддерживаются определенные символы, которые обычно объединяются с другими символами. Например, U + 2160 (РИМСКАЯ ЦИФРА ОДИН) действительно то же самое, что U + 0049 (ЛАТИНСКАЯ ЗАГЛАВНАЯ БУКВА I). Тем не менее, он поддерживается в Юникоде для совместимости с существующими наборами символов, например gb2312.

Нормальная форма KD (NFKD) будет применять декомпозицию совместимости, то есть заменять все символы совместимости их эквивалентами. Нормальная форма KC (NFKC) сначала применяет декомпозицию совместимости, а затем каноническую композицию.

Даже если две строки Юникода нормализованы и выглядят одинаково для человека, если одна имеет комбинированные символы, а другая нет, они могут не совпадать.

import unicodedata 
print(unicodedata.normalize('NFC', A))
# Привет

Проверим тип данных после нормализации:

print(type(unicodedata.normalize('NFC', A)))
# <class 'str'>

Метод 4. Список и str.join

Метод str.join() возвращает строку, которая является конкатенацией (объединением) всех элементов строк итерируемого объекта.

В итоговой строке элементы объединяются между собой при помощи строки-разделителя str.

Если в итерируемой последовательности есть какие-либо НЕ строковые значения, включая байтовые строки, то вызовется исключение TypeError.

Проверим как это работает:

print(''.join([str(i) for i in A]))
# Привет

'' – пустой строковый символ соединяет с помощью метода join элементы списка, который мы составили из элементов строки A.

Поскольку мы указали обернуть каждый перебираемый элемент списка функцией str, можно смело предположить что в результате получим нужный тип данных:

print(type(''.join([str(i) for i in A])))
# <class 'str'>

Метод 5. Библиотека ftfy

Полное название этой библиотеки — Fixes text for you. Она предназначена для того, чтобы превращать плохие строки Юникода(“quotesâ€\x9d или ü) в хорошие строки Юникода(“quotes” или ü соответственно).

Давайте посмотрим как она работает в нашем примере:

import ftfy
print(ftfy.fix_text(A))
# Привет

Что же она делает с типом выходных данных:

print(type(ftfy.fix_text(A)))
# <class 'str'>

Отлично, то что нужно, главное чтобы библиотека оставалась доступной).

Метод 6. Модуль io

Модуль IO применим, когда необходимо выполнить операцию ввода-вывода с файлами (например, чтение или запись файлов). Можно использовать встроенные методы read() и write() для чтения или записи файла, но этот модуль дает нам гораздо больше вариантов кода для этих операций, например, запись или чтение из буфера.

В нашем простом примере это будет выглядеть так:

print(io.StringIO(A).read())
# Привет

io.StringIO работает с данными строкового типа как на входе, так и на выходе. Всякий раз, когда входная строка или поток данных состоит из байтов или символов Юникода, кодирование или декодирование данных выполняется прозрачно, и учитывается необязательный перевод зависящих от среды символов новой строки.

Метод 7. Format

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

Проверим на нашем примере:

print(format(A, 's'))
# Привет

Здесь ‘s’ – тип форматируемого объекта – строка, используется по умолчанию. Более подробно про спецификацию и синтаксис тут.