Python中的Latin-1与Unicode工厂
我有一个Python 2.6的脚本,它在处理从SQL Server数据库中获取的特殊字符时遇到了麻烦,这些字符是用Latin-1编码的。我想打印这些字符,但因为我使用的库调用了unicode
工厂,所以我有点受限,我不知道怎么让Python使用除了ascii
以外的编码。
这个脚本是一个简单的工具,可以从数据库中返回查找数据,而不需要直接在SQL编辑器中执行SQL。我使用PrettyTable 0.5库来显示结果。
脚本的核心部分是这段代码。我从游标获取的元组包含整数和字符串数据,但没有Unicode数据。(我本来可以用adodbapi
代替pyodbc
,这样可以获取Unicode数据,但adodbapi
会带来其他问题。)
x = pyodbc.connect(cxnstring)
r = x.cursor()
r.execute(sql)
t = PrettyTable(columns)
for rec in r:
t.add_row(rec)
r.close()
x.close()
t.set_field_align("ID", 'r')
t.set_field_align("Name", 'l')
print t
但是Name
列可能包含超出ASCII范围的字符。有时候在prettytable.pyc
的第222行,当执行t.add_row
时,我会收到这样的错误信息:
UnicodeDecodeError: 'ascii' codec can't decode byte 0xed in position 12: ordinal not in range(128)
这是prettytable.py
的第222行。它使用了unicode
,这正是我遇到问题的原因,不仅在这个脚本中,在我写的其他Python脚本中也是如此。
for i in range(0,len(row)):
if len(unicode(row[i])) > self.widths[i]: # This is line 222
self.widths[i] = len(unicode(row[i]))
请告诉我我在这里做错了什么。我该如何让unicode
正常工作,而不去修改prettytable.py
或我使用的其他库?有没有办法做到这一点?
编辑:错误发生在t.add_row
调用时,而不是在print
语句。
编辑:在Bastien Léonard的帮助下,我想出了以下解决方案。这不是万能的,但它有效。
x = pyodbc.connect(cxnstring)
r = x.cursor()
r.execute(sql)
t = PrettyTable(columns)
for rec in r:
urec = [s.decode('latin-1') if isinstance(s, str) else s for s in rec]
t.add_row(urec)
r.close()
x.close()
t.set_field_align("ID", 'r')
t.set_field_align("Name", 'l')
print t.get_string().encode('latin-1')
我最终不得不在输入时解码,在输出时编码。所有这些让我希望大家能尽快将他们的库移植到Python 3.x!
3 个回答
看了一下PrettyTable的源代码,发现它内部是用unicode对象来处理的(比如在_stringify_row
、add_row
和add_column
这些地方)。因为它不知道你输入的字符串用的是什么编码,所以它默认使用的是通常是ascii编码。
ascii其实是latin-1的一部分,这意味着如果你是从ascii转换到latin-1,应该不会有什么问题。但是反过来就不一定了;并不是所有的latin-1字符都能对应到ascii字符。为了说明这一点:
>>> s = u'\xed\x31\x32\x33'
>>> print s
# FAILS: Python calls "s.decode('ascii')", but ascii codec can't decode '\xed'
>>> print s.decode('ascii')
# FAILS: Same as above
>>> print s.decode('latin-1')
í123
把字符串明确地转换成unicode(就像你最后做的那样)可以解决问题,而且我觉得这样更合理——你更有可能知道你的数据用的是什么字符集,而不是PrettyTable的作者 :)。顺便说一下,你可以通过把s.decode('latin-1')
换成unicode(s, 'latin-1')
来省略对列表中字符串的检查,因为所有对象都可以转换成字符串。
最后一点:别忘了检查一下你数据库和表的字符集——你可不想在代码里假设用的是'latin-1',结果数据实际上是以其他格式(比如'utf-8')存储在数据库里的。在MySQL中,你可以用SHOW CREATE TABLE <table_name>
命令来查看某个表使用的字符集,用SHOW CREATE DATABASE <db_name>
来查看数据库的字符集。
也许可以试着把用latin1编码的字符串解码成unicode格式?
t.add_row((value.decode('latin1') for value in rec))
在模块的开头加上这个:
# coding: latin1
或者你自己把字符串解码成Unicode格式。
[编辑]
我已经有一段时间没玩Unicode了,但希望这个例子能展示如何把Latin1转换成Unicode:
>>> s = u'ééé'.encode('latin1') # a string you may get from the database
>>> s.decode('latin1')
u'\xe9\xe9\xe9'
[编辑]
文档说明:
http://docs.python.org/howto/unicode.html
http://docs.python.org/library/codecs.html