Как задавать корректные имена кодировок при кодировании строк

Раздел: Строки -> Кодировка

Основные способы работы с названиями кодировок

В Python для преобразования между строками и байтами используются кодировки. Каждая кодировка имеет имя, например utf-8, cp1251, latin-1. Python поддерживает множество синонимов: utf8 равнозначен utf-8, windows-1251 равнозначен cp1251. Правильная работа с именами кодировок помогает избежать ошибок при чтении и записи файлов, сетевой передаче данных и обработке текста.

Как получить каноническое имя кодировки из любого допустимого синонима?

Наиболее надёжный способ – использовать функцию codecs.lookup(). Она принимает строку с именем кодировки и возвращает объект CodecInfo, у которого есть атрибут name – нормализованное каноническое имя. Если имя некорректно, возбуждается LookupError.

import codecs

aliases = ['utf-8', 'utf8', 'UTF8', 'cp1251', 'windows-1251', 'latin-1']
for alias in aliases:
    try:
        canonical = codecs.lookup(alias).name
        print(f'{alias!r} -> {canonical!r}')
    except LookupError:
        print(f'{alias!r} не поддерживается')

Python encoding names (кодировка имен в python)

'utf-8' -> 'utf-8'
'utf8' -> 'utf-8'
'UTF8' -> 'utf-8'
'cp1251' -> 'cp1251'
'windows-1251' -> 'cp1251'
'latin-1' -> 'iso-8859-1'

Типичная проблема:

При передаче имени кодировки, например, из пользовательского ввода, может возникнуть LookupError, если имя не распознано. Например, utf-7 не поддерживается в Python по умолчанию. Всегда используйте блок try/except LookupError для обработки неизвестных имён.

Как быстро закодировать строку, если имя кодировки известно?

Самый простой метод: вызов str.encode(encoding) или bytes.decode(encoding). Python внутри использует тот же механизм codecs.lookup(), поэтому поддерживаются все те же синонимы. Однако этот способ не даёт доступа к каноническому имени кодировки.

text = 'Привет, мир!'
encoded = text.encode('utf8')  # синоним utf-8
print(encoded)
decoded = encoded.decode('utf8')
print(decoded)
b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82, \xd0\xbc\xd0\xb8\xd1\x80!'
Привет, мир!

Частая ошибка:

При попытке декодировать байты, содержащие символы, не представимые в целевой кодировке, возникает UnicodeDecodeError. Например, байты в cp1251 не могут быть декодированы как ascii. Следует указать обработчик ошибок: decode('ascii', errors='replace').

Как узнать, какие кодировки поддерживаются в Python?

Модуль encodings.aliases содержит словарь, где ключи – всевозможные синонимы, а значения – канонические имена. Чтобы получить полный список канонических имён, достаточно извлечь уникальные значения.

from encodings.aliases import aliases

canonical_names = set(aliases.values())
print(f'Всего поддерживаемых кодировок: {len(canonical_names)}')
# Вывод первых 10
for name in sorted(canonical_names)[:10]:
    print(name)
Всего поддерживаемых кодировок: 93
ascii
big5
big5hkscs
cp037
cp1006
cp1026
cp1125
cp1140
cp1250
cp1251

Важно:

Список может различаться в зависимости от платформы (Windows, Linux) и установленных дополнительных модулей (например, cjkcodecs). Не следует полагаться на фиксированный набор.

Как обработать байты с неизвестной кодировкой и не потерять данные?

При декодировании можно указать обработчик ошибок: errors='replace' (заменяет нечитаемые символы на ?), errors='ignore' (пропускает их) или errors='xmlcharrefreplace' (заменяет на XML-сущности). Для записи данных с потерями подходит replace, для сохранения информации – backslashreplace.

data = b'\xff\xfeH\x00e\x00l\x00l\x00o\x00'  # UTF-16 BOM little-endian
try:
    text = data.decode('utf-8')
except UnicodeDecodeError as e:
    print(f'Ошибка: {e}')
    text = data.decode('utf-8', errors='replace')
    print(f'С заменой: {text!r}')
Ошибка: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
С заменой: '���H�e�l�l�o�'

Замечание:

В примере выше байты фактически кодированы в UTF-16, поэтому правильным было бы использовать data.decode('utf-16'). Обработчик replace позволяет избежать аварийного завершения, но искажает данные. Лучше сначала определить кодировку (например, по BOM) и только потом декодировать с соответствующей кодировкой.

Расширенные примеры работы с именами кодировок

Ниже приведены нестандартные сценарии, которые часто встречаются на практике.

Пример
# Определение кодировки по BOM (Byte Order Mark)
import codecs

def detect_encoding_by_bom(data: bytes):
    bom_to_encoding = {
        codecs.BOM_UTF8: 'utf-8',
        codecs.BOM_UTF16_LE: 'utf-16-le',
        codecs.BOM_UTF16_BE: 'utf-16-be',
        codecs.BOM_UTF32_LE: 'utf-32-le',
        codecs.BOM_UTF32_BE: 'utf-32-be',
    }
    for bom, enc in bom_to_encoding.items():
        if data.startswith(bom):
            return enc
    return 'utf-8'  # по умолчанию

# Пример использования
data = b'\xff\xfeH\x00e\x00l\x00l\x00o\x00'
enc = detect_encoding_by_bom(data)
print(f'Обнаружена кодировка: {enc}')
text = data.decode(enc)
print(text)
Обнаружена кодировка: utf-16-le
Hello
Пример
# Чтение файла с указанием кодировки и обработчиком ошибок
import codecs

filename = 'example.txt'
# Предположим, файл содержит текст в cp1251, но мы откроем как utf-8 с заменой
with codecs.open(filename, 'r', encoding='utf-8', errors='replace') as f:
    content = f.read()
    print(content[:100])
Пример
# Нормализация имени кодировки с проверкой
import codecs

def normalize_encoding(name: str) -> str:
    """Возвращает каноническое имя кодировки или None, если не поддерживается."""
    try:
        return codecs.lookup(name).name
    except LookupError:
        return None

tests = ['UTF8', 'cp1251', 'windows-1251', 'latin1', 'invalid-enc']
for t in tests:
    norm = normalize_encoding(t)
    if norm:
        print(f'{t!r} -> {norm!r}')
    else:
        print(f'{t!r} не поддерживается')
'UTF8' -> 'utf-8'
'cp1251' -> 'cp1251'
'windows-1251' -> 'cp1251'
'latin1' -> 'iso-8859-1'
'invalid-enc' не поддерживается
Пример
# Использование кастомного обработчика ошибок для декодирования
import codecs

data = 'Привет'.encode('koi8-r')  # байты в KOI8-R
# Декодируем как ascii с игнорированием, затем повторно декодируем как koi8-r
try:
    text = data.decode('ascii', errors='ignore')  # останутся только ascii-символы
    print('После игнорирования:', text)
except:
    # правильный путь:
    text = data.decode('koi8-r')
    print('Правильное декодирование:', text)
После игнорирования: (пусто, так как все байты не ascii)
Правильное декодирование: Привет
Пример
# Получение списка всех синонимов для заданной канонической кодировки
from encodings.aliases import aliases

canonical = 'utf-8'
synonyms = [alias for alias, canon in aliases.items() if canon == canonical]
print(f'Синонимы для {canonical}: {synonyms[:10]}...')
Синонимы для utf-8: ['utf8', 'utf-8', 'utf_8', 'U8', 'UTF', 'CP65001', ...] (сокращено)
Пример
# Проверка поддержки кодировки перед использованием
encoding_names = ['utf-8', 'cp1251', 'koi8-r', 'iso-8859-2', 'macroman', 'latin-9']
for enc in encoding_names:
    try:
        codecs.lookup(enc)
        print(f'{enc} – поддерживается')
    except LookupError:
        print(f'{enc} – не поддерживается в данной среде')
utf-8 – поддерживается
cp1251 – поддерживается
koi8-r – поддерживается
iso-8859-2 – поддерживается
macroman – поддерживается (на некоторых платформах)
latin-9 – поддерживается
Пример
# Кодирование и декодирование с явным указанием ошибок
text = 'Привет, мир! € € €'
# Попытка закодировать в cp1251 (евро не входит)
encoded = text.encode('cp1251', errors='xmlcharrefreplace')
print(encoded)
print(encoded.decode('cp1251'))
b'\xcf\xf0\xe8\xe2\xe5\xf2, \xec\xe8\xf0! € € €'
Привет, мир! € € €

Кодировка имен в Python - comments

En
Python encoding names (python)