在Python 2.6中使用unicode_literals有什么需要注意的地方吗?

102
我们已经让我们的代码在 Python 2.6 下运行。为了准备 Python 3.0,我们开始在修改 .py 文件时添加:
from __future__ import unicode_literals
我想知道是否有其他人也这样做,并且是否遇到过任何非明显的问题(可能是经过长时间调试后才发现的)。
6个回答

101

我在处理Unicode字符串时,主要遇到的问题是将UTF-8编码的字符串与Unicode字符串混合使用。

例如,请考虑以下脚本。

two.py

# encoding: utf-8
name = 'helló wörld from two'

one.py

# encoding: utf-8
from __future__ import unicode_literals
import two
name = 'helló wörld from one'
print name + two.name

运行 python one.py 的输出结果为:
Traceback (most recent call last):
  File "one.py", line 5, in <module>
    print name + two.name
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 4: ordinal not in range(128)

在这个例子中,two.name是一个utf-8编码的字符串(不是unicode),因为它没有导入unicode_literals,而one.name是一个unicode字符串。当你混合使用两者时,Python会尝试解码编码的字符串(假设它是ascii)并将其转换为unicode,但会失败。如果你执行print name + two.name.decode('utf-8'),就可以解决这个问题。
同样的事情也会发生在你编码一个字符串并尝试之后再混合它们的情况下。例如,以下代码可以正常工作:
# encoding: utf-8
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

输出:

DEBUG: <html><body>helló wörld</body></html>

但是在添加了import unicode_literals之后,它就不会:
# encoding: utf-8
from __future__ import unicode_literals
html = '<html><body>helló wörld</body></html>'
if isinstance(html, unicode):
    html = html.encode('utf-8')
print 'DEBUG: %s' % html

输出:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    print 'DEBUG: %s' % html
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 16: ordinal not in range(128)

它失败了,因为'DEBUG: %s'是一个unicode字符串,因此Python尝试解码html。修复打印的几种方法是:要么执行print str('DEBUG: %s') % html,要么执行print 'DEBUG: %s' % html.decode('utf-8')
希望这能帮助您了解在使用Unicode字符串时可能遇到的问题。

11
我建议使用decode()解决方案而不是str()encode()解决方案:您越经常使用Unicode对象,代码就会更清晰,因为您想要操作的是字符字符串,而不是带有外部隐含编码的字节数组。 - Eric O. Lebigot
8
请修改您的术语。when you mix utf-8 encoded strings with unicode ones UTF-8 和 Unicode 不是两种不同的编码方式;Unicode 是一种标准,而 UTF-8 是它定义的其中一种编码方式。 - Kos
11
@Kos: 我认为他的意思是将“utf-8编码字符串”对象与Unicode(已解码)对象混合在一起。前者属于str类型,后者属于unicode类型。由于它们是不同的对象,如果您尝试将它们相加/连接/插值,可能会出现问题。 - MestreLion
这适用于 python>=2.6 还是 python==2.6 - joar

16

在Python 2.6中(在Python 2.6.5 RC1+之前),unicode字面量与关键字参数不兼容(issue4978):

例如,下面的代码在没有使用unicode_literals的情况下可以正常工作,但是如果使用了unicode_literals,则会出现TypeError: keywords must be string错误。

  >>> def foo(a=None): pass
  ...
  >>> foo(**{'a':1})
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
      TypeError: foo() keywords must be strings

17
只是让您知道,Python 2.6.5 RC1+ 已经解决了这个问题。 - Mahmoud Abdelkader

13

我发现如果你添加了unicode_literals指令,你还应该添加类似于:

 # -*- coding: utf-8

将以下内容添加到您的 .py 文件的第一行或第二行。否则,类似以下行:

 foo = "barré"

会导致错误,例如:

SyntaxError: Non-ASCII character '\xc3' in file mumble.py on line 198,
但未声明编码;参见 http://www.python.org/peps/pep-0263.html 
了解详情

5
Python 3 默认假设文件编码为UTF8。 - endolith
3
Python 2不支持非ASCII字符,即使是在注释中也会出现语法错误。因此,在使用unicode_literals或不使用它的情况下,我认为# -*- coding: utf-8这条语句几乎是必须的。 - MestreLion
-*- 不是必需的;如果你想要兼容emacs的方式,我认为你需要 -*- encoding: utf-8 -*-(注意末尾的 -*-)。你只需要 coding: utf-8(甚至可以用 = 代替 :)。 - Chris Morgan
2
无论您是否使用 from __future__ import unicode_literals,您都会收到此错误。 - Flimm
3
Emacs 兼容性需要在文本开头的文件变量中使用 "# -- coding: utf-8 --" 标记,并且使用 "coding" 而不是其他任何前缀如 "encoding" 或者 "fileencoding"。Python 会查找标记为 "coding" 的字符串来确定编码格式。 - Alex Dupuy

7
还需要考虑到,unicode_literal 将会影响 eval() 而不是 repr()(这种不对称的行为在我看来是一个 bug),即 eval(repr(b'\xa4')) 不等于 b'\xa4'(与 Python 3 中应该相等)。
理想情况下,以下代码应该是一致的,对于所有组合的 unicode_literals 和 Python {2.7, 3.x} 的用法都应该有效:
from __future__ import unicode_literals

bstr = b'\xa4'
assert eval(repr(bstr)) == bstr # fails in Python 2.7, holds in 3.1+

ustr = '\xa4'
assert eval(repr(ustr)) == ustr # holds in Python 2.7 and 3.1+

第二个断言恰好有效,因为在Python 2.7中,repr('\xa4')的计算结果为u'\xa4'

2
我觉得这里更大的问题是你正在使用repr来重新生成一个对象。repr文档明确指出这不是必需的。在我看来,这使得repr只适用于调试。 - jpmc26

5

还有更多。

有一些库和内置函数需要的是不支持unicode编码的字符串。

以下举两个例子:

内置函数:

myenum = type('Enum', (), enum)

(稍微有些奇怪的)在使用unicode_literals时无法工作:type()需要一个字符串。

库:

from wx.lib.pubsub import pub
pub.sendMessage("LOG MESSAGE", msg="no go for unicode literals")

无法正常工作:wx pubsub库需要一个字符串消息类型。

前者很深奥,并且可以很容易地通过以下方式进行修复:

myenum = type(b'Enum', (), enum)

但是如果你的代码中充满了对 pub.sendMessage() 的调用(我的就是),后者会使情况变得严重。

真是让人沮丧啊!


3
类型相关的东西也会泄漏到元类中 - 在 Django 中,你在 class Meta: 中声明的任何字符串都应该是 b'field_name' - Hamish Downer
2
是的...在我的情况下,我意识到花费精力搜索和替换所有sendMessage字符串为b'版本是值得的。如果你想避免可怕的“解码”异常,在程序中严格使用Unicode,并根据需要进行输入和输出转换(在某篇论文中称为“Unicode三明治”)是最好的选择。总的来说,unicode_literals对我来说是一个巨大的胜利... - GreenAsJade

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接