Глава 15 · Python и файлы

Текст, bytes и кодировка UTF-8

Файлы хранят байты. То, как байты превращаются в текст, — это кодировка, и она не универсальна по умолчанию.

Текст и bytes — разные типы

str_vs_bytes.py
print(type("Python"))     # <class 'str'>
print(type(b"Python"))   # <class 'bytes'>
bytes — не «строка без Unicode»
bytes — это последовательность целых чисел от 0 до 255 (двоичные данные), а не урезанная версия строки. Текстовая строка (str) и бинарные данные (bytes) — два разных типа с разным назначением.

Кодирование и декодирование

Чтобы записать текст в файл, его строковое представление превращают в байты — это называется кодированием (encode). При чтении происходит обратное — декодирование (decode):

encode_decode.py
text = "Привет"
b = text.encode("utf-8")
print(b)              # b'\\xd0\\x9f\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82'
print(len(text))      # 6  — шесть символов
print(len(b))         # 12 — двенадцать байт!

obratno = b.decode("utf-8")
print(obratno)        # Привет
str: "Привет"
6 символов
encode("utf-8")
bytes
12 байт
decode("utf-8")
str: "Привет"
6 символов
Кириллица в UTF-8 занимает больше байт, чем символов — реально выполненный пример.
Unicode ≠ UTF-8
Unicode — это система, сопоставляющая символам числовые коды (какой символ значит какое число). UTF-8 — лишь один из способов записать эти числа байтами; есть и другие кодировки. В этом курсе мы всегда явно выбираем UTF-8 как стандарт для текстовых файлов — но это выбор, а не единственно возможный вариант.

Когда формат текста заранее известен как UTF-8 (а в этом курсе это так почти всегда), явное указание encoding="utf-8" делает поведение программы предсказуемым и переносимым — вместо того, чтобы зависеть от кодировки по умолчанию, которая определяется платформой и настройками окружения выполнения.

Почему кодировка важна

Debug Lab 7: Файл открыт без указания encoding
bez_encoding.py
with open("privet.txt", "w") as f:   # без encoding!
    f.write("Привет!")
# Работает по-разному на разных компьютерах и системах —
# кодировка по умолчанию НЕ гарантированно UTF-8 везде.
Что видно на экране

Без явного encoding="utf-8" Python использует кодировку по умолчанию, зависящую от платформы и настроек окружения — а не гарантированно UTF-8. Файл, записанный на одном компьютере, может быть неправильно прочитан на другом, если их кодировки по умолчанию различаются.

Исправленный код
s_encoding.py
with open("privet.txt", "w", encoding="utf-8") as f:
    f.write("Привет!")   # кодировка указана явно и предсказуема всегда
Debug Lab 8: UnicodeDecodeError: неверная кодировка при чтении
nevernaya_kodirovka.py
data = "Привет".encode("utf-8")
with open("soobshenie.bin", "wb") as f:
    f.write(data)

# читаем как текст в ДРУГОЙ кодировке
with open("soobshenie.bin", "r", encoding="ascii") as f:
    print(f.read())
Traceback (most recent call last):
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)
Что видно на экране

Байты были закодированы в UTF-8, а прочитаны как будто в ASCII — кодировки не совпали, и Python не смог превратить эти байты обратно в корректные символы. Решение — читать файл в той же кодировке, в которой он был записан.

Исправленный код
pravilnaya_kodirovka.py
with open("soobshenie.bin", "r", encoding="utf-8") as f:
    print(f.read())   # Привет

Чуть глубже: параметр errors=

У open() есть параметр errors ("strict" по умолчанию, также "replace", "ignore") для случаев несовпадения кодировки.

errors="ignore" не универсальное решение
errors="ignore" молча выбрасывает нераспознанные байты — это может незаметно потерять часть данных. Не используйте его как способ «просто убрать ошибку» без понимания, что именно теряется.
Практика: текст, bytes и UTF-8
Интерактивный ноутбук прямо в браузере — Python 3.14 через Pyodide, без установки
Открыть практику →