Столкнулся с проблемой при подключении к 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.