Проблема хранения Unicode-символов в MySQL с помощью Django - PullRequest
7 голосов
/ 09 июля 2009

У меня есть строка

 u"Played Mirror's Edge\u2122"

Который должен быть показан как

 Played Mirror's Edge™

Но это другая проблема. Моя проблема в том, что я помещаю его в модель, а затем пытаюсь сохранить его в базе данных. AKA:

a = models.Achievement(name=u"Played Mirror's Edge\u2122")
a.save()

И я получаю:

'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

трассировка полного стека (по запросу):

Traceback:
File "/var/home/ptarjan/django/mysite/django/core/handlers/base.py" in get_response
  86.                 response = callback(request, *callback_args, **callback_kwargs)
File "/var/home/ptarjan/django/mysite/yourock/views/alias.py" in import_all
  161.     types.import_all(type, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/types.py" in import_all
  52.     return modules[type].import_all(siteAlias, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/xbox.py" in import_all
  117.             achiever = self.add_achievement(dict, siteAlias, alias)
File "/var/home/ptarjan/django/mysite/yourock/types/base_profile.py" in add_achievement
  130.                 owner       = siteAlias,
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in get
  304.         num = len(clone)
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in __len__
  160.                 self._result_cache = list(self.iterator())
File "/var/home/ptarjan/django/mysite/django/db/models/query.py" in iterator
  275.         for row in self.query.results_iter():
File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py" in results_iter
  206.         for rows in self.execute_sql(MULTI):
File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py" in execute_sql
  1734.         cursor.execute(sql, params)
File "/var/home/ptarjan/django/mysite/django/db/backends/util.py" in execute
  19.             return self.cursor.execute(sql, params)
File "/var/home/ptarjan/django/mysite/django/db/backends/mysql/base.py" in execute
  83.             return self.cursor.execute(query, args)
File "/usr/lib/pymodules/python2.5/MySQLdb/cursors.py" in execute
  151.             query = query % db.literal(args)
File "/usr/lib/pymodules/python2.5/MySQLdb/connections.py" in literal
  247.         return self.escape(o, self.encoders)
File "/usr/lib/pymodules/python2.5/MySQLdb/connections.py" in string_literal
  180.                 return db.string_literal(obj)

Exception Type: UnicodeEncodeError at /import/xbox:bob
Exception Value: 'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

И соответствующая часть модели:

class Achievement(MyBaseModel):
    name = models.CharField(max_length=100, help_text="A human readable achievement name")

Я использую MySQL с этим в моих settings.py

DEFAULT_CHARSET = 'utf-8'

Так в общем, как, черт возьми, я должен иметь дело со всем этим юникодом? Я надеялся, что все это "просто сработает", если я буду держаться подальше от забавных наборов символов и придерживаться UTF8 Увы, это не так просто.

Ответы [ 7 ]

12 голосов
/ 10 июля 2009

Спасибо всем, кто писал здесь. Это действительно помогает моим знаниям юникода (и, к счастью, другие люди чему-то научились).

Мы, похоже, все лаяли не на то дерево, так как я пытался упростить свою проблему и не давал ВСЕЙ информации. Кажется, что я не использовал «РЕАЛЬНЫЕ» строки юникода, а скорее BeautifulSoup.NavigableString, которые воспроизводятся как строки Юникода. Таким образом, все распечатки выглядели как юникод, но не были.

Где-то глубоко в библиотеке MySQLDB они не могли справиться с этими строками.

Это сработало:

>>> Achievement.objects.get(name = u"Mirror's Edge\u2122")
<Achievement: Mirror's Edge™>

С другой стороны:

>>> b = BeautifulSoup(u"<span>Mirror's Edge\u2122</span>").span.string
>>> Achievement.objects.get(name = b)
... Exceptoins ...
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2122' in position 13: ordinal not in range(128)

Но это работает:

>>> Achievement.objects.get(name = unicode(b))
<Achievement: Mirror's Edge™>

Итак, еще раз спасибо за всю помощь по Юникоду, я уверен, что она пригодится. Но пока ...

ПРЕДУПРЕЖДЕНИЕ : BeautifulSoup не возвращает REAL строки Unicode и должен быть приведен к Unicode (), прежде чем делать что-либо значимое с ними.

4 голосов
/ 09 июля 2009

Несколько замечаний:

  • Python 2.x имеет два типа строк

    • "str", который в основном является байтовым массивом (поэтому вы можете хранить в нем все, что вам нравится)
    • "Unicode", который является внутренним кодировкой UCS2 / UCS4
  • Экземпляры этих типов считаются «декодированными» данными. Внутреннее представление является справочным, поэтому вы «декодируете» внешние данные в него и «кодируете» в некоторый внешний формат.

  • Хорошая стратегия - декодировать как можно раньше, когда данные поступают в систему, и кодировать как можно позже. Попробуйте использовать Unicode для строк в вашей системе как можно больше. (Я не согласен с Николаем в этом отношении).

  • Этот аспект кодирования относится к ответу Николая. Он берет исходную строку юникода и кодирует ее в utf-8. Но это не решает проблему (по крайней мере, вообще), потому что результирующий байтовый буфер может все еще содержать байты вне диапазона (127) (я не проверял \ u2122), что означает, что вы снова попадете в то же исключение.

  • Тем не менее, анализ Николая утверждает, что вы передаете строку в кодировке Юникод, но где-то в системе это считается экземпляром str. Достаточно, если где-то функция str () будет применена к аргументу Юникода.

  • В этом случае Python использует так называемую кодировку по умолчанию, которая ascii, если вы ее не измените. Существует функция sys.setdefaultencoding, которую вы можете использовать, например, для переключения. utf-8, но функция доступна только в ограниченном контексте, поэтому ее нелегко использовать в коде приложения.

  • Я чувствую, что проблема где-то глубже в слоях, которые вы вызываете. К сожалению, я не могу комментировать Django или MySQL / SQLalchemy, но мне интересно, можете ли вы указать тип юникода при объявлении атрибута 'name' в вашей модели. Хорошей практикой БД является обработка информации о типе на уровне поля. Может есть альтернатива CharField?!

  • И да, вы можете безопасно вставлять одиночную кавычку (') в строку с двойными кавычками (") и наоборот.

3 голосов
/ 09 июля 2009

Вы используете строки типа «Юникод». Если ваша модель или SQL-сервер не поддерживает их или не знает, как конвертировать в UTF-8, просто сделайте конвертацию самостоятельно. Придерживайтесь простых строк (python type str) и конвертируйте как в

a = models.Achievement(name=u"Played Mirror's Edge\u2122".encode("UTF-8"))
1 голос
/ 09 июля 2009

Вчера я работал над этим и обнаружил, что добавление "charset = utf8" и "use_unicode = 1" в строку подключения делает его работающим (используя SQLAlchemy, думаю, это та же проблема).

Итак, моя строка выглядит так: "MySQL: // пользователь: пароль @ хост: 3306 / база данных use_unicode = 1 & кодировка = utf8"

0 голосов
/ 09 июля 2009

У меня были похожие проблемы с mysql и postgres, но с sqllite проблем не было.

Вот как я решил проблему с postgres (не проверял этот трюк с mysql, но, как я полагаю, он решил бы и эту проблему)

в файле, где вы имеете дело со строкой Unicode, выполните

from django.utils.safestring import SafeUnicode

и предположим, что unistr - это переменная, содержащая строку, сделайте

unistr = SafeUnicode(unistr)

в моем случае я соскребал с сайта

исходный код, который вызывал проблемы (ht - объект Beautifulsoup): -

keyword = ht.a.string

исправление: -

keyword = SafeUnicode(ht.a.string)

Я не знаю, почему или что делает SafeUnicode, я знаю только то, что он решил мои проблемы.

0 голосов
/ 09 июля 2009

Я согласен с Николаем. Я уже сталкивался с проблемой использования UTF-8 даже в чистом Python (2.5).

Я наконец-то использовал функцию Unicode (?):

entry    = unicode(sys.stdin, ENCODING)

Кодирование зависело от локали, если я хорошо помню:

import sys, locale

ENCODING    = locale.getdefaultlocale()[1]
DEFAULT_ENCODING    = sys.getdefaultencoding()

Может быть, взгляните на Python Unicode HOWTO ?

0 голосов
/ 09 июля 2009

Мне кажется, что апостроф выглядит странно, если его не избежать так:

u"Played Mirror\'s Edge\u2122"
...