Python中的Latin-1与Unicode工厂

7 投票
3 回答
48803 浏览
提问于 2025-04-15 13:02

我有一个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 个回答

0

看了一下PrettyTable的源代码,发现它内部是用unicode对象来处理的(比如在_stringify_rowadd_rowadd_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>来查看数据库的字符集。

2

也许可以试着把用latin1编码的字符串解码成unicode格式?

t.add_row((value.decode('latin1') for value in rec))
7

在模块的开头加上这个:

# 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

撰写回答