Python 2: управление текстовыми кодировками
Основы кодировок в Python 2
Python 2 имеет две основные строковые структуры: str (байтовая строка) и unicode (строка в кодировке Unicode). Работа с кодировками часто вызывает трудности из-за неявных преобразований и различий в поведении. Ниже рассматриваются ключевые подходы и типичные сценарии.
Основное рекомендуемое решение – всегда работать с Unicode внутри программы, а на границах (ввод/вывод, чтение файлов, сетевые операции) явно выполнять кодирование и декодирование. Для этого следует:
- Указывать кодировку исходного файла (в первой или второй строке): # -*- coding: utf-8 -*-.
- Использовать from __future__ import unicode_literals – тогда все строковые литералы становятся Unicode.
- Для чтения/записи файлов применять io.open с явным указанием кодировки.
# Файл с явным указанием кодировки
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import io
with io.open('file.txt', 'w', encoding='utf-8') as f:
f.write('Привет, мир!')
with io.open('file.txt', 'r', encoding='utf-8') as f:
data = f.read()
print(type(data)) # <class 'unicode'>Python bytes encoding (кодирование байтов в python)
Этот подход предотвращает большую часть ошибок, связанных с кодировками.
Как работать с файлами в разных кодировках?
Если необходимо прочитать или записать файл в конкретной кодировке (например, cp1251, latin-1), удобно использовать модуль codecs или io.open.
import codecs
# Запись в cp1251
with codecs.open('file_cp1251.txt', 'w', encoding='cp1251') as f:
f.write(u'Привет')
# Чтение из cp1251
with codecs.open('file_cp1251.txt', 'r', encoding='cp1251') as f:
data = f.read()
print(type(data)) # unicodeEncoding decoding python (кодирование и декодирование в python)
Типичная ошибка: при попытке прочитать файл в кодировке, отличной от той, в которой он сохранён, возникает UnicodeDecodeError. Решение – точно узнать кодировку файла (например, с помощью утилиты chardet) и указать её при открытии.
Как избежать UnicodeEncodeError при выводе в консоль?
В Windows консоль часто использует cp866 или cp1251, в Linux – UTF-8. Ошибка возникает, когда байтовая строка содержит символы, не представимые в кодировке консоли. Решение – явно кодировать строку в подходящую кодировку.
s = u'Привет'
print s.encode('utf-8') # только для консоли, поддерживающей UTF-8
# или для Windows:
# print s.encode('cp866')
Encoding python (кодировки в python)
Проблема: если кодировка консоли не совпадает с указанной при выводе, результат будет неправильным. Решение: установить кодировку консоли программно (например, sys.setdefaultencoding – но это опасно) или использовать сторонние библиотеки (например, win-unicode-console).
Как определить кодировку байтовой строки?
Однозначно определить кодировку невозможно, но можно сделать предположение с помощью сторонних библиотек, например chardet.
import chardet
data = '\xff\xfe\x41\x00' # пример BOM для UTF-16
result = chardet.detect(data)
print(result['encoding']) # 'UTF-16'Python 2 encoding (кодировки в python 2)
Ограничение: chardet не гарантирует 100% точность, особенно для коротких строк. Совет: по возможности полагаться на мета-информацию (HTTP заголовки, Content-Type).
Как перевести байтовую строку в Unicode, если кодировка известна?
Используется метод .decode() для str и .encode() для unicode.
byte_str = '\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82' # UTF-8
unicode_str = byte_str.decode('utf-8')
print unicode_str # ПриветPython encode (метод encode в python)
Ошибка: если в строке присутствуют байты, недопустимые в указанной кодировке, вызов decode приведёт к UnicodeDecodeError. Решение: использовать обработчик ошибок, например 'replace' или 'ignore'.
byte_str = '\xff\xfe\x41'
unicode_str = byte_str.decode('utf-8', errors='replace')
print repr(unicode_str) # u'\ufffd\ufffdA' (заменяет недопустимые байты на �)Codecs encode python (модуль codecs.encode в python)
Как использовать from __future__ import unicode_literals для упрощения кода?
Эта директива делает все строковые литералы Unicode (кроме байтовых литералов с префиксом b).
from __future__ import unicode_literals
s = 'эта строка unicode'
print type(s) # <class 'unicode'>
b = b'эта строка bytes'
print type(b) # <class 'str'>
Внимание: при использовании этой директивы код, написанный для Python 2, может сломаться, если он полагается на байтовую природу строк (например, при работе с бинарными протоколами). Решение: явно использовать b'' для байтовых литералов в таких местах.
Расширенные примеры работы с кодировками
Пример 1: Чтение файла в UTF-8 и запись в cp1251 с заменой неподдерживаемых символов
# -*- coding: utf-8 -*-
import io
# Чтение UTF-8
with io.open('source_utf8.txt', 'r', encoding='utf-8') as f:
text = f.read()
# Запись в cp1251 с игнорированием символов, отсутствующих в cp1251
with io.open('output_cp1251.txt', 'w', encoding='cp1251', errors='ignore') as f:
f.write(text)
# Результат: символы вроде '№' будут потеряны
Пояснение: обработчик errors='ignore' просто пропускает символы, которые нельзя представить в указанной кодировке. Альтернатива – 'replace' (заменяет на '?') или 'xmlcharrefreplace' (превращает в NNN;).
Пример 2: Работа с HTTP-ответами, содержащими разные кодировки
import urllib2
# Запрос к странице, возвращающей данные в UTF-8
response = urllib2.urlopen('http://example.com')
html_bytes = response.read()
# Попытка декодировать в UTF-8
html_unicode = html_bytes.decode('utf-8', errors='replace')
# Альтернатива: получить кодировку из заголовка Content-Type
content_type = response.headers.get('Content-Type', '')
if 'charset=' in content_type:
cs = content_type.split('charset=')[1].split(';')[0].strip()
html_unicode2 = html_bytes.decode(cs, errors='replace')
# Результат: html_unicode – строка Unicode, пригодная для дальнейшей обработки.
Пример 3: Использование chardet для автоматического определения кодировки файла
import chardet
def read_file_with_auto_encoding(filename):
with open(filename, 'rb') as f:
raw = f.read()
detected = chardet.detect(raw)
encoding = detected['encoding']
print('Detected encoding:', encoding)
return raw.decode(encoding)
text = read_file_with_auto_encoding('sample.txt')
Пояснение: chardet анализирует образец и возвращает наиболее вероятную кодировку. Для больших файлов можно читать первые несколько килобайт.
Пример 4: Явное указание кодировки при использовании raw_input в консоли Windows
# -*- coding: utf-8 -*-
import sys
# Установка кодировки консоли (не рекомендуется, но для примера)
reload(sys)
sys.setdefaultencoding('utf-8')
# Ввод от пользователя – всегда байтовая строка
user_input = raw_input('Введите текст: ')
# Декодируем в Unicode вручную (предполагаем кодировку консоли)
user_unicode = user_input.decode(sys.stdin.encoding or 'utf-8')
print 'Вы ввели:', user_unicode.encode('utf-8')
Предупреждение: sys.setdefaultencoding может нарушить работу некоторых библиотек и обычно не рекомендуется. Лучше использовать явное декодирование с известной кодировкой консоли.
Пример 5: Объединение байтовых и Unicode-строк – неявное преобразование
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
bytes_str = 'привет'.encode('utf-8') # байтовая строка
unicode_str = u' мир'
# В Python 2 это приведёт к ошибке UnicodeDecodeError при попытке неявного преобразования
# result = bytes_str + unicode_str # Ошибка: 'utf-8' codec can't decode byte ...
# Правильно – явно декодировать байтовую строку
result = bytes_str.decode('utf-8') + unicode_str
print result # привет мир
# Вывод: привет мир (Unicode)
Пример 6: Обработка BOM (Byte Order Mark) в файлах UTF-8/UTF-16
import codecs
# Чтение файла с BOM (например, UTF-8 with BOM)
with codecs.open('file_with_bom.txt', 'r', encoding='utf-8-sig') as f:
data = f.read()
# utf-8-sig автоматически удаляет BOM (\ufeff) из начала
print repr(data[:10])
Пояснение: кодировка 'utf-8-sig' (или 'utf-16' с учётом порядка байт) упрощает работу с документами, содержащими BOM.