python2.7 的中文编码处理,解决UnicodeEncodeError: 'ascii' codec can't encode character 问题

本文主要是介绍python2.7 的中文编码处理,解决UnicodeEncodeError: 'ascii' codec can't encode character 问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

                                    python2.7 的中文编码处理

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

很快,我就遇到了异常:

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

为了解决问题,我花时间去研究了一下 Python 的字符编码处理。

1.引入

对应 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。

由于 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', 编码失败。

解决办法就是你要么给 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 的时候。

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

2.1.stirng 和 unicode 对象合并

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

2.2.列表合并

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

2.3.格式化字符串

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

2.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 <module>print u
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

2.5.输出到文件

>>> f = open('text.txt','w')
>>> f.write(u)
Traceback (most recent call last):File "<input>", line 1, in <module>
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()

参考:https://www.cnblogs.com/huchong/p/9037142.html

https://github.com/scrapinghub/python-crfsuite/issues/96

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

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

这篇关于python2.7 的中文编码处理,解决UnicodeEncodeError: 'ascii' codec can't encode character 问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1123669

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Python使用自带的base64库进行base64编码和解码

《Python使用自带的base64库进行base64编码和解码》在Python中,处理数据的编码和解码是数据传输和存储中非常普遍的需求,其中,Base64是一种常用的编码方案,本文我将详细介绍如何使... 目录引言使用python的base64库进行编码和解码编码函数解码函数Base64编码的应用场景注意

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

springboot报错Invalid bound statement (not found)的解决

《springboot报错Invalidboundstatement(notfound)的解决》本文主要介绍了springboot报错Invalidboundstatement(not... 目录一. 问题描述二.解决问题三. 添加配置项 四.其他的解决方案4.1 Mapper 接口与 XML 文件不匹配