Python 2和3中的UTF-8字符串

7
以下代码适用于Python 3:
people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

并生成以下输出:
Nicholas Gyeney, André  
Writers: Nicholas Gyeney, André

在Python 2.7中,我得到了以下错误:
Traceback (most recent call last):
  File "python", line 4, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' 
in position 21: ordinal not in range(128)

我可以通过将", ".join(people)改为", ".join(people).encode('utf-8')来解决此错误,但如果这样做,在Python 3中的输出会更改为:
b'Nicholas Gyeney, Andr\xc3\xa9'  
Writers: b'Nicholas Gyeney, Andr\xc3\xa9'

所以我尝试使用以下代码:

if sys.version_info < (3, 0):
    reload(sys)
    sys.setdefaultencoding('utf-8')

people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

使用这种方法可以使我的代码在所有版本的Python中工作。但我读到了使用 setdefaultencoding 不被鼓励 的内容。
处理这个问题的最佳方法是什么?

1
处理这个问题的最佳方法是停止尝试编写能在两种语言中运行的脚本,而是选择其中一种。Python 2和Python 3不兼容。 - TigerhawkT3
如果您正在使用Unicode字符串进行文本处理,请对所有文本使用Unicode字符串...或者切换到Python 3,它会强制您正确地这样做。 - Mark Tolonen
4个回答

12

我们假设您想支持Python 2.7和3.5版本(2.6和3.0至3.2的处理方式略有不同)。

正如您已经阅读的那样,setdefaultencoding 是不建议使用并且在您的情况下实际上不需要。

为了编写跨平台处理unicode文本的代码,一般只需在以下几个地方指定字符串编码:

  1. 在脚本顶部,在shebang下方添加# -*- coding: utf-8 -*-(仅当代码中有带有unicode文本的字符串字面量时)
  2. 读取输入数据时(例如从文本文件或数据库读取)
  3. 输出数据时(再次从文本文件或数据库中)
  4. 在代码中定义字符串字面量时

这是我按照这些规则更改示例的方法:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

people = ['Nicholas Gyeney', 'André']
writers = ", ".join(people)
print(writers)
print("Writers: {}".format(writers))

print(type(writers))
print(len(writers))

输出结果为:

<type 'str'>
23

以下是更改的内容:

  • 在文件顶部指定文件编码
  • 用实际的Unicode字符(é)替换\xe9
  • 移除u前缀

在Python 2.7.12和3.5.2中都很好用。

但要注意,移除u前缀将使Python使用普通的str类型而不是unicode类型(请参见print(type(writers))输出)。对于utf-8,它在大多数地方的表现就像一个unicode字符串,但当检查文本长度时,会返回错误的值。在这个例子中,len返回23,而实际字符数是22。这是因为底层类型是str,它将每个字节都计算为一个字符,但是字符é实际上应该是两个字节。

换句话说,在输出数据时,这样做可以正常工作(如您的示例所示),但如果要对文本进行字符串操作,则仍然需要使用u前缀或将数据显式转换为unicode类型。

因此,如果不是因为您的简单示例,最好仍然使用u前缀。您需要在两个地方添加它:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

people = [u'Nicholas Gyeney', u'André']
writers = ", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))

print(type(writers))
print(len(writers))

它的输出结果为:

<type 'unicode'>
22

注意:Python 3.0 中删除了 u 前缀,但为了向后兼容性在 Python 3.3 中重新引入。

有关在 Python 2 中处理 Unicode 文本的所有复杂性的详细说明,请参阅官方文档:Python 2 - Unicode HOWTO

以下是有关指定文件编码的特殊注释的摘录:

 

Python 支持使用任何编码编写 Unicode 字面量,但必须声明正在使用的编码方式。这可以通过将一个特殊注释作为源文件的第一行或第二行来实现:

#!/usr/bin/env python
# -*- coding: latin-1 -*-

u = u'abcdé' print ord(u[-1])

该语法受到 Emacs 用于指定文件局部变量的表示法的启发。Emacs 支持许多不同的变量,但 Python 只支持 coding-*- 符号指示 Emacs 该注释是特殊的;它们在 Python 中没有意义,但是是一种约定。Python 在注释中查找 coding: namecoding=name

如果您不包含这样的注释,则使用的默认编码将是 ASCII。

如果您获得了书籍 "Learning Python, 5th Edition",我鼓励您阅读第 VIII 部分的第 37 章 "Unicode 和字节字符串"。它详细解释了在 Python 的两个版本中处理 Unicode 文本的方法。

另一个值得一提的细节是,如果格式字符串是 asciiformat 始终返回 ascii 字符串,无论参数是否为 unicode

相反,旧式的 % 格式化如果任何参数为 unicode,则返回 unicode 字符串。因此,代替写下面这段代码:

print(u"Writers: {}".format(writers))

你可以写出以下代码,不仅较短、美观,而且在Python 2和3上都可行:

print("Writers: %s" % writers)

非常感谢。更具体地说,André 是从 Web 服务返回的,我没有在代码中将其定义为字符串文字。 - B Faley

3
您可以在格式化时提供Unicode前缀:
print(u"Writers: {}".format(writers))

这确实解决了问题,但是您的Python 3脚本中有很多不必要的u''前缀,这样会让代码显得混乱。

您也可以在检查版本后使用from __future__ import unicode_literals,但我不建议这样做,因为它通常更加棘手,而且自从u''前缀足以胜任工作后,已经考虑将其弃用。


2
在Python2中,您应该使用Unicode字符串进行joinprint操作:
people = [u'Nicholas Gyeney', u'Andr\xe9']
writers = u", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))

.join中这样做是多余的,因为你已经在连接Unicode字符串了。 - Dimitris Fasarakis Hilliard
@Jim 这样做多余但准确。如果混合使用字节字符串和Unicode字符串,Python 3会报错。养成正确的习惯。也许连接器中含有非ASCII字符。 - Mark Tolonen

0

答案是将所有内容都转换为Unicode编码:

# -*- coding: utf-8 -*-
people = [u'Nicholas Gyeney', u'André']
writers = u", ".join(people)
print(writers)
print(u"Writers: {}".format(writers))

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