Про кодировки: Должна остаться только одна!

Sep 19, 2013 17:44 · 460 words · 3 minute read python

Сегодня пришлось мне повозиться с кодировкой строк. Задача: записать в базу русские буквы. Реализация: Python (MySQLdb) + MySQL + OS X. Не зря я здесь указал платформу ;) Казалось бы, тривиальная задача, ан нет - в таблицу падали крякозябры.

Сначала я грешил на питон с его странными строками (Python 2.7). Известно, что обычная строка там - просто последовательность байтов. Получить юникодную строку можно несколькими способами:

  1. указать в начале файла # -*- coding: UTF-8 -*-, т.о. весь файл будет читаться как UTF-8;
  2. явно указать, что строка юникодная: u'я юникод';
  3. сконвертировать в нужную кодировку: "Текст".decode('utf-8').

На последнем пункте стоит остановиться подробнее. Конвертацию куда мы указали, а откуда - нет. На самом-то деле берётся текущая кодировка сессии. Для Windows это CP866 или Windows-1251 (русская локализация), для linux latin-1 или UTF-8 (всё чаще), для OS X - latin-1 (aka Windows-1252). Таким образом, decode переводит строку из кодировки консоли в то, что указано в первом параметре. Есть ещё и второй параметр - что делать с неизвестными символами, но тут уже лучше сходить к документации. Есть ещё метод encode - он делает обратное преобразование: из кодировки в параметре в последовательность байт. В общем, запутаться очень просто :) На помощь может прийти библиотека chardet - она позволяет определить кодировку строки (с определённой вероятностью):

>>> a = 'sdfds'
>>> import chardet
>>> print chardet.detect(a)
{'confidence': 1.0, 'encoding': 'ascii'}
>>> a = 'авыаыв'
>>> print chardet.detect(a)
{'confidence': 0.99, 'encoding': 'utf-8'}

Ужас, да? :) Спешу обрадовать: в Python 3.x такого безобразия нет, там все строки в UTF-8.

Но вернёмся к нашей проблеме: скрипт в utf, кодировка строк не указана => кодировка скрипта => utf, база в utf, таблица в utf, даже поле в utf. Так почему же пишется мусор?! Оказалось всё до банальности просто - не была указана кодировка для соединения с базой, а она по умолчанию берётся не из настроек MySQL (где указана как utf_general_ci), а из сессии! И тут вспоминаем, что мы работаем под OS X, а там по умолчанию она равна latin-1. Проверяем в декодере Лебедева: действительно, строка в Windows-1252 записана как UTF-8.

Вроде проблему закрыли, какие выводы? Во-первых, везде используйте utf-8 (если, конечно, объём не критичен); во-вторых, везде указывайте явно нужную вам кодировку - не полагайтесь на значение по умолчанию; в-третьих, строка в Python 2.x - последовательность байт (decode во что перегнать, encode - откуда). Вот, вроде бы и всё :) Выпьем же за разработчиков UTF-8 и победу этой чудесной кодировки!

P.S. Полезные ссылки:

  1. Юникод для чайников. Там же ответ почему PHP не поддерживает юникод из коробки.
  2. Про работу с MySQL и DBF из Python.
  3. Про кодировку в консоли.

P.P.S. На самом деле не такая уж UTF и замечательная кодировка. Понять и запомнить все нюансы, по-моему, просто невозможно… Но всё это можно скрыть за слоями абстракций, а свою главную задачу - унификацию - она всё-таки решает.