Справочник по Python · модуль 5 · строки
Глава 5.4
Кодирование строк:
encode() и decode()
Текст в программе имеет тип str, а файл, сеть и база данных передают байты. encode() кодирует символы в bytes, decode() восстанавливает текст по той же кодировке. Несовпадение кодировок даёт либо исключение, либо испорченный текст.
- Категория
- Строки и байты
- Уровень
- Средний
- Связанные темы
- Unicode, файлы, сеть, исключения
- Зачем это знать
- Корректно переводить текст в передаваемые байты и обратно
§1Зачем нужны байты
Внутри программы строка — это последовательность символов, и глава 5.1 показала, что за каждым символом стоит номер в таблице Unicode. Но ни файл, ни сетевое соединение символов не знают: они умеют передавать только байты — числа от 0 до 255.
в программе на диске и в сети
str ←→ bytes
"Привет" ←→ b'\xd0\x9f\xd1\x80...'
encode
decode
Кодировка — это правило перевода между двумя мирами: какими байтами записывается каждый символ. Пока текст живёт в переменной, кодировка не важна. Она становится важной ровно в двух точках: когда текст покидает программу и когда возвращается в неё.
§2str и bytes
Это два разных типа с разным содержимым и разными методами.
| Тип | Содержимое | Литерал | Метод перевода |
|---|---|---|---|
str | символы Unicode | "Привет" | encode() |
bytes | числа от 0 до 255 | b"hello" | decode() |
Направление методов запомнить легко: encode() есть только у строки и уводит из текста в байты, decode() есть только у байтов и возвращает обратно. Обратного не бывает — у str нет метода decode(), у bytes нет encode().
§3Литерал b и его содержимое
Байты записываются как строка с буквой b перед кавычкой. Внутри — не текст, а числа: печатаемые ASCII-символы Python показывает как есть, остальные — в шестнадцатеричном виде.
data = b"AB" print(data) print(data[0]) print(list(data))
b'AB' 65 [65, 66]
Число 65 знакомо по главе 5.1 — это кодовая точка буквы A. Для символов ASCII байт и кодовая точка совпадают, и именно поэтому байты выглядят «как текст». Для кириллицы совпадения уже нет.
§4encode(): str в bytes
encode(encoding="utf-8") переводит строку в байты по указанной кодировке.
text = "Привет" data = text.encode("utf-8") print(data) print(type(data))
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' <class 'bytes'>
Каждая пара вида \xd0\x9f — это два байта, записанных шестнадцатерично. Аргумент можно опустить: text.encode() означает то же самое, что text.encode("utf-8"). Явное указание кодировки надёжнее — оно документирует намерение.
§5decode(): bytes в str
Обратный метод принадлежит объекту bytes и требует указать, по какой кодировке читать байты.
data = "Привет".encode("utf-8") text = data.decode("utf-8") print(text) print(type(text))
Привет <class 'str'>
В b'\xd0\x9f' нет пометки «это UTF-8». Кодировку сообщает формат обмена — заголовок HTTP, спецификация файла, договорённость между системами. Если её никто не сообщил, остаётся угадывать, и это главный источник испорченного текста.
§6Полный маршрут
Пара методов обратима, пока кодировка с обеих сторон одна и та же.
original = "Цена: 500 ₽" payload = original.encode("utf-8") restored = payload.decode("utf-8") print(restored == original)
True
§7Длина в символах и в байтах
Это то место, где расхождение двух миров впервые становится заметным.
text = "Я" data = text.encode("utf-8") print(len(text)) print(len(data)) print(data)
1 2 b'\xd0\xaf'
Для целой фразы разница накапливается.
text = "Цена: 500 ₽" print(len(text)) print(len(text.encode("utf-8")))
11 17
Практический вывод: если внешнее ограничение задано в байтах — размер поля в базе, лимит SMS, максимальная длина сообщения в протоколе, — проверять его через len(text) нельзя. Считать нужно len(text.encode("utf-8")).
§8Сколько занимает символ
UTF-8 — кодировка переменной длины: разные символы занимают разное число байтов. Это её главное свойство и причина популярности.
| Символ | Байтов в UTF-8 | Что за диапазон |
|---|---|---|
A, 7, пробел | 1 | латиница и знаки ASCII |
Я, ё | 2 | кириллица, греческий, иврит |
₽, € | 3 | знаки валют, иероглифы |
| эмодзи | 4 | дополнительные плоскости Unicode |
Отсюда важное следствие: обратиться к «третьему символу» через индекс байтов нельзя. Индекс в bytes — это позиция байта, и она не совпадает с позицией символа. Резать текст нужно до кодирования, а не после.
§9Выбор кодировки
| Кодировка | Что поддерживает | Замечание |
|---|---|---|
utf-8 | весь Unicode | стандартный выбор для новых форматов |
ascii | 128 базовых символов | кириллица не входит вовсе |
latin-1 | 256 позиций, по байту на символ | декодирует любые байты, но чаще всего неверно |
cp1251 | кириллица, по байту на символ | встречается в старых файлах и базах |
Практическое правило: для нового кода — всегда utf-8. Остальные кодировки нужны, когда вы читаете чужие данные и их формат задан заранее.
text = "привет" print(text.encode("utf-8")) print(text.encode("cp1251"))
b'\xd0\xbf\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' b'\xef\xf0\xe8\xe2\xe5\xf2'
Одна и та же строка превращается в совершенно разные наборы байтов. Поэтому байты без указанной кодировки — это данные без смысла.
§10Ошибка кодирования
UnicodeEncodeError возникает, когда в кодировке просто нет нужного символа.
print("Привет".encode("ascii"))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)
Сообщение точное: указаны позиции проблемных символов и причина — их кодовые точки выходят за range(128). Это ошибка на выходе из программы.
§11Ошибка декодирования
UnicodeDecodeError — зеркальный случай: байты пришли, но по выбранной кодировке они не складываются в символ.
data = "Я".encode("utf-8") print(data.decode("ascii"))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)
Здесь ошибка сработала как защита: неверная кодировка обнаружена сразу. К сожалению, так везёт не всегда — об этом следующий параграф.
§12Тихая порча текста
Кодировка latin-1 отображает все 256 значений байта, поэтому декодировать ею можно любые данные. Исключения не будет — будет бессмыслица.
data = "Привет".encode("utf-8") print(data.decode("latin-1"))
ÐÑивеÑ
Такой результат называют «кракозябрами». Каждый байт двухбайтового символа прочитан как отдельная буква, поэтому шесть букв превратились в двенадцать знаков. Программа продолжает работать, данные молча испорчены — и обнаружится это обычно у пользователя.
Успешный decode() не означает, что кодировка выбрана верно. Проверять нужно результат: если в тексте появились Ð, Ñ, Â или вопросительные знаки в ромбах, кодировка не та.
Хорошая новость: такая порча обратима, пока текст не сохранён дальше. Достаточно проделать обратный путь.
broken = "Привет".encode("utf-8").decode("latin-1") fixed = broken.encode("latin-1").decode("utf-8") print(fixed)
Привет
§13Параметр errors
Второй параметр обоих методов задаёт, что делать с символами, которые не переводятся. По умолчанию — strict, то есть исключение.
text = "Цена €" print(text.encode("ascii", errors="ignore")) print(text.encode("ascii", errors="replace")) print(text.encode("ascii", errors="backslashreplace"))
b' ' b'???? ?' b'\\u0426\\u0435\\u043d\\u0430 \\u20ac'
| Значение | Что делает | Когда уместно |
|---|---|---|
strict | вызывает исключение | по умолчанию; данные важны |
ignore | молча выбрасывает символ | почти никогда |
replace | подставляет ? | логи, отладочный вывод |
backslashreplace | записывает код символа | отладка: видно, что потерялось |
Первая строка вывода особенно показательна: от "Цена €" остался один пробел. Режим ignore удалил всё, что не поместилось, и не сообщил об этом. Ставить его «чтобы не падало» — верный способ потерять данные незаметно.
§14Где это встречается
В обычном коде encode() и decode() вызывают редко: файловые и сетевые библиотеки делают это сами. Но кодировку они берут из параметра, и вот там о ней придётся вспомнить.
| Ситуация | Где задаётся кодировка |
|---|---|
| чтение и запись файла | open(path, encoding="utf-8") |
| ответ веб-сервера | заголовок Content-Type |
| подключение к базе данных | параметры соединения |
| лимит на размер сообщения | len(text.encode("utf-8")) |
Работа с файлами разбирается в модуле 8. Здесь достаточно запомнить: если функция принимает параметр encoding, его лучше указать явно, а не полагаться на настройки системы, которые на другой машине окажутся иными.
§15Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
text.decode() | AttributeError: у str нет такого метода | декодировать объект bytes |
data.encode() | AttributeError: у bytes нет такого метода | кодировать str |
| кодировать в UTF-8, декодировать как ASCII | UnicodeDecodeError | использовать согласованную кодировку |
декодировать чужие байты как latin-1 | ошибки не будет, текст превратится в кракозябры | узнать настоящую кодировку источника |
| считать один символ одним байтом | лимит размера вычислится неверно | len(text.encode("utf-8")) |
резать bytes по индексу как текст | символ разорвётся посередине | резать строку до кодирования |
errors="ignore", чтобы «не падало» | непереводимые символы исчезнут молча | strict и обработка исключения |
полагаться на кодировку по умолчанию в open() | на другой машине текст прочитается иначе | указывать encoding="utf-8" явно |
§16Проверьте себя
Какой тип возвращает encode()?
bytes — последовательность чисел от 0 до 255.
Почему кодировку нужно знать при декодировании?
Байты не содержат сведений о себе: одни и те же значения в разных кодировках означают разные символы.
Почему len("Я") равно 1, а len("Я".encode("utf-8")) — 2?
Первое считает символы, второе — байты. В UTF-8 кириллическая буква занимает два байта.
Что опаснее: UnicodeDecodeError или успешное декодирование не той кодировкой?
Второе. Исключение сразу указывает на проблему, а тихая порча обнаружится только по испорченному тексту у пользователя.
Чем плох errors="ignore"?
Он молча удаляет данные: от "Цена €" в ASCII останется один пробел, и никакого сообщения об этом не будет.
Можно ли взять третий символ строки из её байтов по индексу 2?
Нет. В UTF-8 символы занимают разное число байтов, поэтому позиция байта не совпадает с позицией символа.
§17Шпаргалка
data = text.encode("utf-8")text = data.decode("utf-8")len(text.encode("utf-8"))open(path, encoding="utf-8")errors="backslashreplace"bad.encode("latin-1").decode("utf-8")Итог главы
encode() создаёт байты из строки, decode() создаёт строку из байтов, и обратимость требует одной и той же кодировки с обеих сторон. Сами байты своей кодировки не хранят — её задаёт формат обмена. Несовпадение проявляется двумя способами: исключением, что удобно, или тихой порчей текста, что гораздо хуже. Для нового кода выбор один — utf-8, указанный явно.