Столкнулся с проблемой при подключении к MySQL с помощью пакета MySQLdb для python. При указании кодировки, содержащей знак минус в своём названии:
db = MySQLdb.connect(user = 'user', passwd = 'p4assw0rd', db = 'db', charset = 'koi8r')
Пакет отказывается работать, вываливая трассировку:
Traceback (most recent call last): File "./myapp.py", line 559, in <module> charset = 'koi8-r') File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/__init__.py", line 81, in Connect return Connection(*args, **kwargs) File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 215, in __init__ self.set_character_set(charset) File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 294, in set_character_set super(Connection, self).set_character_set(charset) _mysql_exceptions.OperationalError: (2019, "Can't initialize character set koi8-r (path: /usr/local/share/mysql/charsets/)")
Я знаю, что в MySQL приняты названия кодировок, из которых выкинуты минусы. Однако, если указать кодировку без минуса, то пакет всё равно отказывается работать, выдавая несколько другой трейсбэк:
Traceback (most recent call last): File "./myapp.py", line 612, in <module> merge_lists(list1, list2) File "./myapp.py", line 576, in merge_lists value = myfunction(list['key']) File "./myapp.py", line 514, in myfunction WHERE param = %s''', (value,)) File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursors.py", line 159, in execute query = query % db.literal(args) File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 264, in literal return self.escape(o, self.encoders) File "/usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py", line 202, in unicode_literal return db.literal(u.encode(unicode_literal.charset)) LookupError: unknown encoding: koi8r
Что характерно, для кодировки UTF-8 один из вариантов написания всё-же работает. Почитал, что об этом пишут в интернете. Один умный анонимный товарищ объяснил ситуацию так:
А не MySQLdb ли там у Вас крутится?
если да - то тут две проблемы:
- В mysql принято считать, что они лучше знают названия кодировок.
- Модуль MySQLdb для питона считает, что в mysql действительно знают названия кодировок и верит, что python в курсе, что кодировка KOI8-R называется "koi8r", хотя это далеко не так.
В модуле codecs python'а кодировка KOI8-R называется "koi8-r", что вообще-то соответствует стандарту. python любит кодировать все строки в unicode. После чего, если они пытаются передаться в mysql, модуль MySQLdb перекодирует из unicode в кодировку, установленную для соединения. А там 'koi8r', вот и ошибка вылетает.
Это лечится отстрелом авторов модуля MySQLdb. Хотя впрочем, можно и всех mysqlистых ребят...
Это подтвердило мои собственные догадки. С момента написания этого высказывания прошло уже почти 5 лет, но ничего не изменилось. Видимо, это не нужно ни пользователям модуля, ни его разработчикам. Честно говоря, мне тоже не нужно - если бы у меня была возможность выбора, то я бы выбрал UTF-8, но у меня такой возможности не было.
Отредактируем модуль connections.py, входящий в пакет MySQLdb:
# vi /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py
И отредактируем метод set_character_set, как показано ниже:
def set_character_set(self, charset): """Set the connection character set to charset. The character set can only be changed in MySQL-4.1 and newer. If you try to change the character set from the current value in an older version, NotSupportedError will be raised.""" mysql_charset = charset.replace('-', '') if self.character_set_name() != mysql_charset: try: super(Connection, self).set_character_set(mysql_charset) except AttributeError: if self._server_version < (4, 1): raise NotSupportedError("server is too old to set charset") self.query('SET NAMES %s' % mysql_charset) self.store_result() self.string_decoder.charset = charset self.unicode_literal.charset = charset
Пересобирём модуль, чтобы им можно было пользоваться:
# python -m compileall /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/connections.py
Теперь откроем файл cursors.py:
# vi /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursor.py
И добавим метод character_set_name() в класс BaseCursor:
def character_set_name(): db = self._get_db() charset = db.character_set_name() map = {'koi8r': 'koi8-r', 'koi8u': 'koi8-u'} return map.get(charset.lower(), charset)
А во всех следующих ниже методах заменим строки
db = self._get_db() charset = db.character_set_name()
на строку:
charset = self.character_set_name()
И перекомпилируем модуль:
# python -m compileall /usr/local/lib/python2.7/site-packages/MySQL_python-1.2.3-py2.7-freebsd-8.2-RELEASE-p4-amd64.egg/MySQLdb/cursors.py
С такими костылями всё работает нормально, во всяком случае на ошибки я при этом не натыкался. При необходимости можно добавить дополнительные отображения названий кодировок в словарь map.