`
in355hz
  • 浏览: 229886 次
社区版块
存档分类
最新评论

也谈 Python 的中文编码处理

阅读更多

最近业务中需要用 Python 写一些脚本。尽管脚本的交互只是命令行 + 日志输出,但是为了让界面友好些,我还是决定用中文输出日志信息。

 

很快,我就遇到了异常:

 

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

 

为了解决问题,我花时间去研究了一下 Python 的字符编码处理。网上也有不少文章讲 Python 的字符编码,但是我看过一遍,觉得自己可以讲得更明白些。

 

下面先复述一下 Python 字符串的基础,熟悉此内容的可以跳过。

 

对应 C/C++ 的 char 和 wchar_t, Python 也有两种字符串类型,str 与 unicode:

 

# -*- coding: utf-8 -*-
# file: example1.py
import string

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

print isinstance(s, str)      # True
print isinstance(u, unicode)  # True

print s.__class__   # <type 'str'>
print u.__class__   # <type 'unicode'>

 

前面的申明:# -*- coding: utf-8 -*- 表明,上面的 Python 代码由 utf-8 编码。

 

为了保证输出不会在 linux 终端上显示乱码,需要设置好 linux 的环境变量:export LANG=en_US.UTF-8

 

如果你和我一样是使用 SecureCRT,请设置 Session Options/Terminal/Appearance/Character Encoding 为 UTF-8 ,保证能够正确的解码 linux 终端的输出。

 

两个 Python 字符串类型间可以用 encode / decode 方法转换:

 

# 从 str 转换成 unicode
print s.decode('utf-8')   # 关关雎鸠

# 从 unicode 转换成 str
print u.encode('utf-8')   # 关关雎鸠

 

为什么从 unicode 转 str 是 encode,而反过来叫 decode? 

 

因为 Python 认为 16 位的 unicode 才是字符的唯一内码,而大家常用的字符集如 gb2312,gb18030/gbk,utf-8,以及 ascii 都是字符的二进制(字节)编码形式。把字符从 unicode 转换成二进制编码,当然是要 encode。

 

反过来,在 Python 中出现的 str 都是用字符集编码的 ansi 字符串。Python 本身并不知道 str 的编码,需要由开发者指定正确的字符集 decode。

 

(补充一句,其实 Python 是可以知道 str 编码的。因为我们在代码前面申明了 # -*- coding: utf-8 -*-,这表明代码中的 str 都是用 utf-8 编码的,我不知道 Python 为什么不这样做。)

 

如果用错误的字符集来 encode/decode 会怎样?

 

# 用 ascii 编码含中文的 unicode 字符串
u.encode('ascii')  # 错误,因为中文无法用 ascii 字符集编码
                   # UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

# 用 gbk 编码含中文的 unicode 字符串
u.encode('gbk')  # 正确,因为 '关关雎鸠' 可以用中文 gbk 字符集表示
                 # '\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf'
                 # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的

# 用 ascii 解码 utf-8 字符串
s.decode('ascii')  # 错误,中文 utf-8 字符无法用 ascii 解码
                   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

# 用 gbk 解码 utf-8 字符串
s.decode('gbk')  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码
                 # u'\u934f\u51b2\u53e7\u95c6\u5ea8\u7b2d'

 

这就遇到了我在本文开头贴出的异常:UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

 

现在我们知道了这是个字符串编码异常。接下来, 为什么 Python 这么容易出现字符串编/解码异常? 

 

这要提到处理 Python 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

 

# -*- coding: utf-8 -*-
# file: example2.py

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

s + u  # 失败,UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

 

简单的字符串连接也会出现解码错误?

 

陷阱一:在进行同时包含 str 与 unicode 的运算时,Python 一律都把 str 转换成 unicode 再运算,当然,运算结果也都是 unicode。

 

由于 Python 事先并不知道 str 的编码,它只能使用 sys.getdefaultencoding() 编码去 decode。在我的印象里,sys.getdefaultencoding() 的值总是 'ascii' ——显然,如果需要转换的 str 有中文,一定会出现错误。

 

除了字符串连接,% 运算的结果也是一样的:

 

# 正确,所有的字符串都是 str, 不需要 decode
"中文:%s" % s   # 中文:关关雎鸠

# 失败,相当于运行:"中文:%s".decode('ascii') % u
"中文:%s" % u  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

# 正确,所有字符串都是 unicode, 不需要 decode
u"中文:%s" % u   # 中文:关关雎鸠

# 失败,相当于运行:u"中文:%s" % s.decode('ascii')
u"中文:%s" % s  # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

 

我不理解为什么 sys.getdefaultencoding() 与环境变量 $LANG 全无关系。如果 Python 用 $LANG 设置 sys.getdefaultencoding() 的值,那么至少开发者遇到 UnicodeDecodeError 的几率会降低 50%。

 

另外,就像前面说的,我也怀疑为什么 Python 在这里不参考 # -*- coding: utf-8 -*- ,因为 Python 在运行前总是会检查你的代码,这保证了代码里定义的 str 一定是 utf-8 。

 

对于这个问题,我的唯一建议是在代码里的中文字符串前写上 u。另外,在 Python 3 已经取消了 str,让所有的字符串都是 unicode ——这也许是个正确的决定。

 

其实,sys.getdefaultencoding() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

 

# -*- coding: utf-8 -*-
# file: example3.py
import sys

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

# 使得 sys.getdefaultencoding() 的值为 'utf-8'
reload(sys)                      # reload 才能调用 setdefaultencoding 方法
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'

# 没问题
s + u  # u'\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20'

# 同样没问题
"中文:%s" % u   # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'

# 还是没问题
u"中文:%s" % s  # u'\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20'

 

可以看到,问题魔术般的解决了。但是注意! sys.setdefaultencoding() 的效果是全局的,如果你的代码由几个不同编码的 Python 文件组成,用这种方法只是按下了葫芦浮起了瓢,让问题变得复杂。

 

另一个陷阱是有关标准输出的。

 

刚刚怎么来着?我一直说要设置正确的 linux $LANG 环境变量。那么,设置错误的 $LANG,比如 zh_CN.GBK 会怎样?(避免终端的影响,请把 SecureCRT 也设置成相同的字符集。)

 

显然会是乱码,但是不是所有输出都是乱码。

 

# -*- coding: utf-8 -*-
# file: example4.py
import string

# 这个是 str 的字符串
s = '关关雎鸠'

# 这个是 unicode 的字符串
u = u'关关雎鸠'

# 输出 str 字符串, 显示是乱码
print s   # 鍏冲叧闆庨笭

# 输出 unicode 字符串,显示正确
print u  # 关关雎鸠

 

为什么是 unicode 而不是 str 的字符显示是正确的? 首先我们需要了解 print。与所有语言一样,这个 Python 命令实际上是把字符打印到标准输出流 —— sys.stdout。而 Python 在这里变了个魔术,它会按照 sys.stdout.encoding 来给 unicode 编码,而把 str 直接输出,扔给操作系统去解决。

 

这也是为什么要设置 linux $LANG 环境变量与 SecureCRT 一致,否则这些字符会被 SecureCRT 再转换一次,才会交给桌面的 Windows 系统用编码 CP936 或者说 GBK 来显示。

 

通常情况,sys.stdout.encoding 的值与 linux $LANG 环境变量保持一致:

 

# -*- coding: utf-8 -*-
# file: example5.py
import sys

# 检查标准输出流的编码
print sys.stdout.encoding  # 设置 $LANG = zh_CN.GBK,  输出 GBK
                           # 设置 $LANG = en_US.UTF-8,输出 UTF-8

# 这个是 unicode 的字符串
u = u'关关雎鸠'

# 输出 unicode 字符串,显示正确
print u  # 关关雎鸠

 

但是,这里有 陷阱二:一旦你的 Python 代码是用管道 / 子进程方式运行,sys.stdout.encoding 就会失效,让你重新遇到 UnicodeEncodeError。

 

比如,用管道方式运行上面的 example4.py 代码:

 

python -u example5.py | more

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)
None

 

可以看到,第一:sys.stdout.encoding 的值变成了 None;第二:Python 在 print 时会尝试用 ascii 去编码 unicode.

 

由于 ascii 字符集不能用来表示中文字符,这里当然会编码失败。

 

怎么解决这个问题? 不知道别人是怎么搞定的,总之我用了一个丑陋的办法:

 

# -*- coding: utf-8 -*-
# file: example6.py
import os
import sys
import codecs

# 无论如何,请用 linux 系统的当前字符集输出:
if sys.stdout.encoding is None:
    enc = os.environ['LANG'].split('.')[1]
    sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替换 sys.stdout

# 这个是 unicode 的字符串
u = u'关关雎鸠'

# 输出 unicode 字符串,显示正确
print u  # 关关雎鸠

 

这个方法仍然有个副作用:直接输出中文 str 会失败,因为 codecs 模块的 writer 与 sys.stdout 的行为相反,它会把所有的 str 用 sys.getdefaultencoding() 的字符集转换成 unicode 输出。

 

# 这个是 str 的字符串
s = '关关雎鸠'

# 输出 str 字符串, 异常
print s   # UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 0: ordinal not in range(128)

 

显然,sys.getdefaultencoding() 的值是 'ascii', 编码失败。

 

解决办法就像 example3.py 里说的,你要么给 str 加上 u 申明成 unicode,要么通过“后门”去修改 sys.getdefaultencoding():

 

# 使得 sys.getdefaultencoding() 的值为 'utf-8'
reload(sys)                      # reload 才能调用 setdefaultencoding 方法
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'

# 这个是 str 的字符串
s = '关关雎鸠'

# 输出 str 字符串, OK
print s   # 关关雎鸠

 

总而言之,在 Python 2 下进行中文输入输出是个危机四伏的事,特别是在你的代码里混合使用 str 与 unicode 时。

 

有些模块,例如 json,会直接返回 unicode 类型的字符串,让你的 % 运算需要进行字符解码而失败。而有些会直接返回 str, 你需要知道它们的真实编码,特别是在 print 的时候。

 

为了避免一些陷阱,上文中说过,最好的办法就是在 Python 代码里永远使用 u 定义中文字符串。另外,如果你的代码需要用管道 / 子进程方式运行,则需要用到 example6.py 里的技巧。

 

(完)

分享到:
评论

相关推荐

    浅谈python下含中文字符串正则表达式的编码问题

    在Python编程中,处理含有中文字符串的正则表达式时,编码问题是编程者需要面对的常见问题之一。由于Python的默认编码格式为ASCII,它无法直接处理中文字符,因为ASCII编码集中并未包含中文字符。因此,在需要处理...

    浅谈Python2之汉字编码为unicode的问题(即类似\xc3\xa4)

    在了解Python2中的汉字编码问题之前,首先需要明确字符编码的基本概念。字符编码是将字符集中的字符映射到计算机中可以处理的数字串的方式。常见的字符编码包括ASCII、GBK、UTF-8等。其中,Unicode是一个为世界上...

    浅谈Python2获取中文文件名的编码问题

    通过使用`chardet`等工具检测文件名的编码,并进行正确的解码,可以有效地避免乱码问题的发生。同时,考虑到不同操作系统之间的差异,需要编写兼容性强的代码来确保程序能够在多种环境下稳定运行。

    浅谈Python爬取网页的编码处理

    Python爬取网页时,编码处理是一个关键步骤,因为网页的编码方式多种多样,不正确的处理会导致乱码。本文主要探讨如何解决Python爬虫在处理网页编码时遇到的问题。 首先,我们要理解编码的基本概念。在计算机中,...

    浅谈编码,解码,乱码的问题

    在编程世界里,编码、解码以及乱码问题是最常见的挑战之一。编码是将字符转换为数字或二进制码流的过程,而解码则是这个过程的逆向操作,将码流还原为字符。理解这些概念是解决乱码问题的关键。 首先,我们需要明确...

    浅谈python处理json和redis hash的坑

    作者还提到了处理JSON数据时中文乱码的问题,并且指出在使用json.dumps()函数时,通过设置ensure_ascii参数为False可以解决中文乱码的问题。 当Redis的maxmemory达到设置的最大值时,它会随机删除设置了过期时间的...

    python 浅谈serial与stm32通信的编码问题

    在查了可用编码后,发现可能是中文无法解码的问题,使用’gbk’–统一汉语进行解码,可以解决乱码问题。  utf-8虽然是通用语言编码标准,但中文出现乱码可以使用有针对性的’gbk’进行解码。 除此之外,python官方...

    浅谈Python中的字符串

    `str` 类型使用ASCII编码,只能表示英文字符,而 `unicode` 类型使用Unicode编码,可支持所有字符,包括中文、日文等。默认情况下,Python字符串为 `str` 类型,若需创建 `unicode` 字符串,可在字符串前加上 `u` 或...

    浅谈python的elementtree模块处理中文注意事项

    在处理包含中文字符的XML文档时,`elementtree`模块需要注意一些特殊的编码问题,以确保中文字符能够正确地读取和写入。 首先,当使用`elementtree`模块写入XML文件时,一定要注意指定正确的编码格式。XML文件的...

    再谈Python中的字符串与字符编码(推荐)

    在Python中,字符串和字符编码是编程中必不可少的基础...在Python 3中,由于其对Unicode的内置支持,使得处理中文和其他语言变得更加方便,但也需要开发者对Unicode和字节编码有足够的理解,才能更好地利用这一特性。

    浅谈文字编码和Unicode

    然而,随着全球化的推进,需要处理多种语言的需求日益增长,这就催生了更复杂的编码系统,如ISO-8859-1(支持西欧语言)和GB2312(用于简体中文)。这些编码方式虽然解决了特定语种的问题,但不同编码间的互操作性却...

    Python核心编程第二版

    很不错的python书 第1部分 Python核心  第1章 欢迎来到Python世界   1.1 什么是Python   1.2 起源   1.3 特点   1.3.1 高级   1.3.2 面向对象   1.3.3 可升级   1.3.4 可扩展   1.3.5 可...

    浅谈python中对于json写入txt文件的编码问题

    Python中处理编码问题尤其重要,尤其是当我们将JSON数据写入到文本文件中时。本文将详细探讨Python中处理JSON数据写入文本文件时遇到的编码问题,并提供解决方案。 首先,什么是JSON?JSON(JavaScript Object ...

    浅谈Python脚本开头及导包注释自动添加方法

    它告诉Python解释器文件采用UTF-8编码,确保程序能正确识别和处理这些特殊字符,避免出现编码错误。 为了方便地在创建新的Python脚本时自动添加这些头部注释,你可以利用IDE(集成开发环境)的配置功能。以PyCharm...

    浅谈python str.format与制表符\t关于中文对齐的细节问题

    总结来说,处理中文对齐问题的关键在于理解字符编码和字节的关系,以及`str.format`和制表符`\t`的特性。在实际应用中,可能还需要考虑到不同编码方式的影响,如UTF-8和GBK。通过巧妙地结合这些工具,我们可以有效地...

    浅谈python中str字符串和unicode对象字符串的拼接问题

    总的来说,理解和正确处理`str`和`unicode`字符串在Python中的拼接是解决多语言文本处理问题的关键。掌握这些基础知识,能够帮助你在编写处理复杂文本数据的程序时避免出现编码错误,提高代码的健壮性和可维护性。

    浅谈mysql的中文乱码问题

    MySQL的中文乱码问题是一个常见的困扰开发者的问题,尤其是在进行数据导入、导出或者通过不同的工具与MySQL交互时。本文将详细解析这个问题,并提供相应的解决策略。 首先,问题出现在使用Eclipse通过JDBC向MySQL...

Global site tag (gtag.js) - Google Analytics