Работа с Unicode и кодировками строк в Python
Основы работы с Unicode в Python
Python 3 использует тип str для представления текста в кодировке Unicode, а тип bytes для последовательностей байтов. Для преобразования между ними применяются методы .encode() и .decode(). Наиболее эффективное решение - всегда указывать кодировку явно, чаще всего utf-8, так как она поддерживает все символы Unicode и эффективна по памяти.
Пример базового преобразования:
text = 'Привет, мир!'
encoded = text.encode('utf-8')
print(encoded)
decoded = encoded.decode('utf-8')
print(decoded)Python unicode (unicode в python)
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, \xd0\xbc\xd0\xb8\xd1\x80!' Привет, мир!
Метод .encode() возвращает байтовый объект. Явное указание кодировки делает код предсказуемым и предотвращает ошибки на разных платформах.
Как использовать другую кодировку, например cp1251?
Иногда требуется работа с устаревшими кодировками:
text = 'Привет'
encoded_cp1251 = text.encode('cp1251')
print(encoded_cp1251)
decoded_cp1251 = encoded_cp1251.decode('cp1251')
print(decoded_cp1251)b'\xcf\xf0\xe8\xe2\xe5\xf2' Привет
Важно: не все символы Unicode представимы в cp1251, что вызовет ошибку.
Что делать, если символ не умещается в кодировку?
Параметр errors позволяет указать стратегию обработки:
text = 'Hello ☺'
# strict (по умолчанию) вызовет UnicodeEncodeError для ascii
try:
text.encode('ascii')
except UnicodeEncodeError as e:
print(f'Ошибка: {e}')
# ignore удаляет неподдерживаемые символы
print(text.encode('ascii', errors='ignore'))
# replace заменяет на вопросительный знак
print(text.encode('ascii', errors='replace'))
# backslashreplace использует escape-последовательности
print(text.encode('ascii', errors='backslashreplace'))
# xmlcharrefreplace заменяет на HTML-сущности
print(text.encode('ascii', errors='xmlcharrefreplace'))Ошибка: 'ascii' codec can't encode character '\u263a' in position 6: ordinal not in range(128) b'Hello ' b'Hello ?' b'Hello \u263a' b'Hello ☺'
Выбор стратегии зависит от задачи: ignore подходит для фильтрации, replace для сохранения длины строки.
Как привести строку к канонической форме?
Разные Unicode-символы могут выглядеть одинаково, но иметь разную кодировку (например, буква «ё» как один символ или как «е» + диакритический знак). Модуль unicodedata предоставляет нормализацию:
import unicodedata
s1 = 'café' # один символ é
s2 = 'cafe\u0301' # e + combining acute accent
print(s1 == s2) # False
s2_nfc = unicodedata.normalize('NFC', s2)
print(s1 == s2_nfc) # TrueFalse True
Формы NFC (составная) и NFD (разложенная) наиболее распространены. Нормализация полезна при сравнении строк и работе с текстом из разных источников.
Как работать с символами вне базовой плоскости (суррогатные пары)?
Эмодзи и редкие иероглифы занимают две 16-битные единицы. Python работает с ними корректно, но важно учитывать длину строки:
emoji = '?' # U+1F600
print(len(emoji)) # 1 (символ)
print(len(emoji.encode('utf-8'))) # 4 байта
print(list(emoji)) # ['?']1 4 ['?']
При работе с индексами Python автоматически оперирует целыми кодовыми точками, поэтому срез emoji[0] вернёт весь смайлик, а не половину.
Типичные ошибки:
- UnicodeEncodeError - попытка закодировать символ, отсутствующий в целевой кодировке. Решение: использовать ‘utf-8’ или указать подходящий errors.
- UnicodeDecodeError - попытка декодировать байты с неверной кодировкой. Решение: определить правильную кодировку или использовать errors='replace'.
- TypeError - смешивание str и bytes (например, конкатенация). Решение: явно преобразовывать к одному типу.
- Путаница с длиной: len(str) считает символы, len(bytes) - байты. Следует помнить, что один символ может занимать от 1 до 4 байт в UTF-8.
Расширенные примеры работы с Unicode
Чтение и запись файлов с указанием кодировки
# Запись в файл в UTF-8
with open('hello.txt', 'w', encoding='utf-8') as f:
f.write('Привет, мир! ☺')
# Чтение с другой кодировкой (например, латиница)
with open('hello.txt', 'r', encoding='utf-8') as f:
content = f.read()
print(content) # Привет, мир! ☺Привет, мир! ☺
Если не указать encoding, будет использована системная кодировка, что может привести к ошибкам на разных ОС.
Автоматическое определение кодировки с помощью chardet
import chardet
# Некоторый байтовый поток (например, из файла)
b_data = b'\xff\xfeP\x00r\x00i\x00v\x00e\x00t\x00'
result = chardet.detect(b_data)
print(result)
# {'encoding': 'UTF-16LE', 'confidence': 1.0, 'language': ''}
decoded = b_data.decode(result['encoding'])
print(decoded) # Привет{'encoding': 'UTF-16LE', 'confidence': 1.0, 'language': ''}
ПриветБиблиотека chardet полезна при работе с данными неизвестной кодировки.
Использование юникодных escape-последовательностей
# Представление строки через \u и \U
text = '\u041f\u0440\u0438\u0432\u0435\u0442' # Привет
print(text)
# Для символов вне BMP используем \U0001F600
emoji = '\U0001F600' # ?
print(emoji)Привет ?
Этот подход удобен для вставки символов, которые нельзя набрать напрямую.
Нормализация в реальном сценарии: сравнение строк
import unicodedata
s1 = 'café'
s2 = 'cafe\u0301' # канонически эквивалентная форма
# Прямое сравнение
print(s1 == s2) # False
# Нормализация обеих строк к NFC
s1_nfc = unicodedata.normalize('NFC', s1)
s2_nfc = unicodedata.normalize('NFC', s2)
print(s1_nfc == s2_nfc) # TrueFalse True
Без нормализации две казалось бы одинаковые строки могут считаться разными.
Регулярные выражения с Unicode
import re
text = 'Русский текст с цифрами 123 и эмодзи ?'
# Поиск всех слов (букв Unicode)
pattern = re.compile(r'\w+', re.UNICODE)
print(pattern.findall(text))
# ['Русский', 'текст', 'с', 'цифрами', '123', 'и', 'эмодзи']['Русский', 'текст', 'с', 'цифрами', '123', 'и', 'эмодзи']
Флаг re.UNICODE заставляет \w совпадать с любыми буквами Unicode, включая кириллицу.
Работа с кодовыми точками напрямую
# Получение кода символа
code = ord('?')
print(hex(code)) # 0x1f600
# Создание символа из кода
char = chr(0x1f600)
print(char) # ?
# Суррогатная пара (внутреннее представление Python скрывает суррогаты)
s = '\U0001F600'
print(len(s)) # 1
print(hex(ord(s))) # 0x1f6000x1f600 ? 1 0x1f600
Функции ord() и chr() работают с полными кодовыми точками, а не с суррогатными единицами.
Кодирование с использованием модуля codecs
import codecs
# Кодирование в hex с разделителями
hex_str = codecs.encode('Привет', 'hex')
print(hex_str) # b'd09f d180 d0b8 d0b2 d0b5 d182' (в зависимости от кодировки)
# Декодирование из unicode-escape
s = codecs.decode(b'\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442', 'unicode-escape')
print(s) # Приветb'd09fd180d0b8d0b2d0b5d182' Привет
Модуль codecs предоставляет гибкие методы для работы с различными кодеками.
Обработка ошибок при декодировании с заменой
# Байты, содержащие как корректные, так и некорректные данные
b = b'\xc3\xa9\xc3\x28' # первый байт é, второй - неполный
# strict
try:
b.decode('utf-8')
except UnicodeDecodeError as e:
print(f'Ошибка: {e}')
# replace
decoded = b.decode('utf-8', errors='replace')
print(decoded) # é� (замена на �)Ошибка: 'utf-8' codec can't decode byte 0xc3 in position 2: invalid continuation byte é�
При работе с повреждёнными данными стратегия replace позволяет получить строку, не прерывая выполнение.