热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python2.7的中文编码处理,解决UnicodeEncodeError:'ascii'codeccan'tencodecharacter问题

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

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

 

很快,我就遇到了异常:

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

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

 

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

 1.引入

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

example1.py  

# -*- 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__ #
print u.__class__ #

 

前面的申明:# -*- 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 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

example2.py  

# -*- 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() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

 example3.py  

# -*- 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 也设置成相同的字符集。)

 

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

 example4.py  

# -*- 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 环境变量保持一致:

example5.py  

# -*- 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 字符集不能用来表示中文字符,这里当然会编码失败。

 

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

example6.py  

# -*- 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 里的技巧。

 

 2.python 自动解编码机制导致报错

1.stirng 和 unicode 对象合并

>>> s + u''
Traceback (most recent call last):
File
"", line 1, in
UnicodeDecodeError:
'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>

2.列表合并

 

>>> as_list = [u, s]
>>> ''.join(as_list)
Traceback (most recent call last):
File
"", line 1, in
UnicodeDecodeError:
'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

3.格式化字符串

>>> '%s-%s'%(s,u)
Traceback (most recent call last):
File
"", line 1, in
UnicodeDecodeError:
'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>>

4.打印 unicode 对象

#test.py
#
-*- coding: utf-8 -*-
u = u'中文'
print u
#outpt
Traceback (most recent call last):
File
"/Users/zhyq0826/workspace/zhyq0826/blog-code/p20161030_python_encoding/uni.py", line 3, in
print u
UnicodeEncodeError:
'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

5.输出到文件

>>> f = open('text.txt','w')
>>> f.write(u)
Traceback (most recent call last):
File
"", line 1, in
UnicodeEncodeError:
'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
>>>

1,2,3 的例子中,python 自动用 ascii 把 string 解码为 unicode 对象然后再进行相应操作,所以都是 decode 错误, 4 和 5 python 自动用 ascii 把 unicode 对象编码为字符串然后输出,所以都是 encode 错误。

只要涉及到 unicode 对象和 string 的转换以及 unicode 对象输出、输入的地方可能都会触发 python 自动进行解码/编码,比如写入数据库、写入到文件、读取 socket 等等。

到此,这两个异常产生的真正原因了基本已经清楚了: unicode 对象需要编码为相应的 string(字符串)才可以存储、传输、打印,字符串需要解码为对应的 unicode 对象才能完成 unicode 对象的各种操作,lenfind 等。

string.decode('utf-8') --> unicode
unicode.encode(
'utf-8') --> string

 

3.如何避免这些的错误

1.理解编码或解码的转换方向

无论何时发生编码错误,首先要理解编码方向,然后再针对性解决。

2.设置默认编码为 utf-8

在文件头写入

# -*- coding: utf-8 -*-

python 会查找: coding: name or coding=name,并设置文件编码格式为 name,此方式是告诉 python 默认编码不再是 ascii ,而是要使用声明的编码格式。

3.输入对象尽早解码为 unicode,输出对象尽早编码为字节流

无论何时有字节流输入,都需要尽早解码为 unicode 对象。任何时候想要把 unicode 对象写入到文件、数据库、socket 等外界程序,都需要进行编码。

4.使用 codecs 模块来处理输入输出 unicode 对象

codecs 模块可以自动的完成解编码的工作。

>>> import codecs
>>> f = codecs.open('text.txt', 'w', 'utf-8')
>>> f.write(u)
>>> f.close()

 

参考:

http://in355hz.iteye.com/blog/1860787

http://sanyuesha.com/2016/11/06/python-string-unicode/


推荐阅读
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • docker镜像重启_docker怎么启动镜像dock ... [详细]
  • 探讨在 JavaScript 中使用不同方向的 for 循环来实现跟随鼠标的 div 动画时,为什么会出现不同的视觉效果。 ... [详细]
  • 利用决策树预测NBA比赛胜负的Python数据挖掘实践
    本文通过使用2013-14赛季NBA赛程与结果数据集以及2013年NBA排名数据,结合《Python数据挖掘入门与实践》一书中的方法,展示如何应用决策树算法进行比赛胜负预测。我们将详细讲解数据预处理、特征工程及模型评估等关键步骤。 ... [详细]
  • 丽江客栈选择问题
    本文介绍了一道经典的算法题,题目涉及在丽江河边的n家特色客栈中选择住宿方案。两位游客希望住在色调相同的两家客栈,并在晚上选择一家最低消费不超过p元的咖啡店小聚。我们将详细探讨如何计算满足条件的住宿方案总数。 ... [详细]
  • 本文介绍 SQL Server 的基本概念和操作,涵盖系统数据库、常用数据类型、表的创建及增删改查等基础操作。通过实例帮助读者快速上手 SQL Server 数据库管理。 ... [详细]
  • 本文介绍如何使用MFC和ADO技术调用SQL Server中的存储过程,以查询指定小区在特定时间段内的通话统计数据。通过用户界面选择小区ID、开始时间和结束时间,系统将计算并展示小时级的通话量、拥塞率及半速率通话比例。 ... [详细]
  • 全面解析运维监控:白盒与黑盒监控及四大黄金指标
    本文深入探讨了白盒和黑盒监控的概念,以及它们在系统监控中的应用。通过详细分析基础监控和业务监控的不同采集方法,结合四个黄金指标的解读,帮助读者更好地理解和实施有效的监控策略。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 深入解析动态代理模式:23种设计模式之三
    在设计模式中,动态代理模式是应用最为广泛的一种代理模式。它允许我们在运行时动态创建代理对象,并在调用方法时进行增强处理。本文将详细介绍动态代理的实现机制及其应用场景。 ... [详细]
  • QNX 微内核(procnto-instr)的监测版本内置了高级跟踪与分析工具,能够实现实时系统监控。该模块适用于单处理器及多处理器系统。 ... [详细]
author-avatar
他w与他说
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有