001_Python2 的中文编码处理
最近业务中需要用 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 里的技巧。
(完)
001_Python2 的中文编码处理的更多相关文章
- 难道.NET Core到R2连中文编码都不支持吗?
今天写了一个简单的.NET Core RC2控制台程序,发现中文显示一直是乱码.查看操作系统设置,没有问题:查看源文件编码,也没有问题:甚至查看了Console字符编码相关的注册表,依然没有发现问题. ...
- Java Web中的中文编码
Java Web开发中经常会遇到中文编码问题,那么为什么需要编码呢?因为人类需要表示的符号太多,无法用1个字节来表示,而计算机中存储信息最小单元为1个字节.所以必须指定char与byte之间的编码规则 ...
- Java页面中文编码要转换两次encodeURI
1.js文件中使用encodeURI()方法. login_name = encodeURI(encodeURI(login_name)); 2.action中URLDecoder解码 loginNa ...
- ZKUI中文编码以及以docker方式运行的问题
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- R语言读写中文编码方式
最近遇到一个很头疼的事,就是 R语言读写中文编码方式.在网上找到了一篇博文,谢谢博主的精彩分享,让我很快解决了问题,在此也分享一下 R语言读写数据的方法很多,这里主要是我在使用read.csv/rea ...
- python中文编码
前面章节中我们已经学会了如何用 Python 输出 "Hello, World!",英文没有问题,但是如果你输出中文字符"你好,世界"就有可能会碰到中文编码问题 ...
- Java中文编码小结
Java中文编码小结 1. 只有 字符到字节 或者 字节到字符 的转换才存在编码转码; 2. Java String 采用 UTF-16 编码方式存储所有字符.unicode体系采用唯一的码点表示唯一 ...
- python--爬虫入门(七)urllib库初体验以及中文编码问题的探讨
python系列均基于python3.4环境 ---------@_@? --------------------------------------------------------------- ...
- [python基础]关于中文编码和解码那点事儿
我们在用python处理中文的时候,或多或少会遇到这样一些错误 常见错误1: SyntaxError: Non-ASCII character '\xe4' in file C 常见错误2: Unic ...
随机推荐
- html5基础!!
A:HTML5 不基于 SGML,所以不需要引用 DTD: B: HTML 4.01 基于 SGML,而HTML5不基于SGML: DTD可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文 ...
- cf24D. Broken robot(高斯消元)
题意 题目链接 Sol 今天上午的A题.想出来怎么做了但是没时间写了qwq 思路很简单,首先把转移方程列一下,发现每一个位置只会从下一行/左右转移过来,而且第N行都是0,那么往下转移的都可以回带. 剩 ...
- Django之Ajax文件上传
请求头ContentType ContentType指的是请求体的编码类型,常见的类型共有3种: 1 application/x-www-form-urlencoded(看下图) 这应该是最常见的 P ...
- 性能测试 CentOS下结合InfluxDB及Grafana图表实时展示JMeter相关性能数据
CentOS下结合InfluxDB及Grafana图表实时展示JMeter相关性能数据 by:授客 QQ:1033553122 实现功能 1 测试环境 1 环境搭建 2 1.安装influxdb ...
- JNI NDK (AndroidStudio+CMake )实现C C++调用Java代码流程
JNI/NDK Java调用C/C++前言 通过第三篇文章讲解在实际的开发过程中Java层调用C/C++层的处理流程.其实我们在很大的业务里也需要C/C+ +层去调用Java层,这两层之间的相互调用 ...
- 章节四、2-Switch语句
package introduction5; public class SwitchDemo { //switch用于固定值的判断,如星期.人的性别 //if用于判断区间.范围,能够用switch进行 ...
- (后台)jxl.read.biff.BiffException: Unable to recognize OLE stream
在excel中打开,另存成xls就可以.
- 报错org.apache.hadoop.mapreduce.lib.input.FileSplit cannot be cast to org.apache.hadoop.mapred.FileSplit
报错 java.lang.Exception: java.lang.ClassCastException: org.apache.hadoop.mapreduce.lib.input.FileSpli ...
- Apache kylin 入门
本篇文章就概念.工作机制.数据备份.优势与不足4个方面详细介绍了Apache Kylin. Apache Kylin 简介 1. Apache kylin 是一个开源的海量数据分布式预处理引擎.它通过 ...
- Elasticsearch拼音分词和IK分词的安装及使用
一.Es插件配置及下载 1.IK分词器的下载安装 关于IK分词器的介绍不再多少,一言以蔽之,IK分词是目前使用非常广泛分词效果比较好的中文分词器.做ES开发的,中文分词十有八九使用的都是IK分词器. ...