python - 使用Django将Unicode字符存储到MySQL的问题
我有一个字符串
u"Played Mirror's Edge\u2122"
应该显示成
Played Mirror's Edge™
不过那是另一个问题。现在我遇到的问题是,我把它放进一个模型里,然后试着保存到数据库。也就是说:
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")
我在settings.py中使用的是MySQL作为后端
DEFAULT_CHARSET = 'utf-8'
所以,基本上,我该怎么处理这些unicode的东西呢?我本以为只要不碰那些奇怪的字符集,使用UTF8就能“顺利进行”。可惜,这似乎没有那么简单。
7 个回答
你正在使用'unicode'类型的字符串。如果你的模型或者SQL后台不支持这种字符串,或者不知道怎么转换成UTF-8格式,那你就自己来转换。最好使用简单的字符串(Python中的str类型),然后像下面这样进行转换:
a = models.Achievement(name=u"Played Mirror's Edge\u2122".encode("UTF-8"))
几点说明:
Python 2.x 有两种字符串类型:
- “str”,基本上就是一个字节数组(所以你可以在里面存储任何你想要的东西)
- “unicode”,内部是用 UCS2/UCS4 编码的 Unicode 字符
这些类型的实例被认为是“解码”的数据。内部表示是引用,所以你要把外部数据“解码”到里面,然后再“编码”成某种外部格式。
一个好的策略是尽早解码数据,当数据进入系统时尽量早解码,尽量晚编码。尽量在你的系统中使用 Unicode 字符串。(在这方面我和 Nikolai 的看法不同)。
编码这个方面适用于 Nikolai 的回答。他把原始的 Unicode 字符串编码成 utf-8。但这并不能解决问题(至少不是一般情况下),因为生成的字节缓冲区仍然可能包含超出范围(127)的字节(我没有检查 \u2122),这意味着你会再次遇到同样的异常。
不过 Nikolai 的分析是正确的,你传递的是一个 Unicode 字符串,但在系统的某个地方,这被视为一个 str 实例。如果在某个地方对你的 Unicode 参数应用了 str() 函数,就足够了。
在这种情况下,Python 会使用所谓的默认编码,如果你不改变它,就是 ascii。有一个函数 sys.setdefaultencoding 可以用来切换到例如 utf-8,但这个函数只在有限的上下文中可用,所以你不能轻易在应用代码中使用它。
我觉得问题可能出在你调用的层次结构的更深处。不幸的是,我无法对 Django 或 MySQL/SQLalchemy 发表评论,但我想知道在你的模型中声明 'name' 属性时,是否可以指定一个 Unicode 类型。处理字段级别的类型信息是好的数据库实践。也许 CharField 还有其他替代方案?
是的,你可以安全地在双引号(")字符串中嵌入单引号('),反之亦然。
感谢大家在这里发言。这对我了解unicode有很大帮助(希望其他人也能学到点东西)。
我们似乎都在误解问题,因为我试图简化我的问题,没有提供所有信息。其实我并没有使用“真正的”unicode字符串,而是使用了BeautifulSoup中的NavigableString,它们看起来像unicode字符串。结果所有的打印输出都像是unicode,但实际上并不是。
在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™>
再次感谢大家对unicode的帮助,我相信这会派上用场。不过现在……
警告:BeautifulSoup返回的不是真正的unicode字符串,在进行任何有意义的操作之前,应该用unicode()来转换它们。