001_Python2 的中文编码处理

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

很快,我就遇到了异常:

Python代码  

  1. 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:

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example1.py
  3. import string
  4. # 这个是 str 的字符串
  5. s = ‘关关雎鸠‘
  6. # 这个是 unicode 的字符串
  7. u = u‘关关雎鸠‘
  8. print isinstance(s, str)      # True
  9. print isinstance(u, unicode)  # True
  10. print s.__class__   # <type ‘str‘>
  11. 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 方法转换:

Python代码  

  1. # 从 str 转换成 unicode
  2. print s.decode(‘utf-8‘)   # 关关雎鸠
  3. # 从 unicode 转换成 str
  4. 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 会怎样?

Python代码  

  1. # 用 ascii 编码含中文的 unicode 字符串
  2. u.encode(‘ascii‘)  # 错误,因为中文无法用 ascii 字符集编码
  3. # UnicodeEncodeError: ‘ascii‘ codec can‘t encode characters in position 0-3: ordinal not in range(128)
  4. # 用 gbk 编码含中文的 unicode 字符串
  5. u.encode(‘gbk‘)  # 正确,因为 ‘关关雎鸠‘ 可以用中文 gbk 字符集表示
  6. # ‘\xb9\xd8\xb9\xd8\xf6\xc2\xf0\xaf‘
  7. # 直接 print 上面的 str 会显示乱码,修改环境变量为 zh_CN.GBK 可以看到结果是对的
  8. # 用 ascii 解码 utf-8 字符串
  9. s.decode(‘ascii‘)  # 错误,中文 utf-8 字符无法用 ascii 解码
  10. # UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe5 in position 0: ordinal not in range(128)
  11. # 用 gbk 解码 utf-8 字符串
  12. s.decode(‘gbk‘)  # 不出错,但是用 gbk 解码 utf-8 字符流的结果,显然只是乱码
  13. # 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 编码时容易遇到的两个陷阱。第一个是有关字符串连接的:

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example2.py
  3. # 这个是 str 的字符串
  4. s = ‘关关雎鸠‘
  5. # 这个是 unicode 的字符串
  6. u = u‘关关雎鸠‘
  7. 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 有中文,一定会出现错误。

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

Python代码  

  1. # 正确,所有的字符串都是 str, 不需要 decode
  2. "中文:%s" % s   # 中文:关关雎鸠
  3. # 失败,相当于运行:"中文:%s".decode(‘ascii‘) % u
  4. "中文:%s" % u  # UnicodeDecodeError: ‘ascii‘ codec can‘t decode byte 0xe5 in position 0: ordinal not in range(128)
  5. # 正确,所有字符串都是 unicode, 不需要 decode
  6. u"中文:%s" % u   # 中文:关关雎鸠
  7. # 失败,相当于运行:u"中文:%s" % s.decode(‘ascii‘)
  8. 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() 的值是可以用“后门”方式修改的,我不是特别推荐这个解决方案,但是还是贴一下,因为后面有用:

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example3.py
  3. import sys
  4. # 这个是 str 的字符串
  5. s = ‘关关雎鸠‘
  6. # 这个是 unicode 的字符串
  7. u = u‘关关雎鸠‘
  8. # 使得 sys.getdefaultencoding() 的值为 ‘utf-8‘
  9. reload(sys)                      # reload 才能调用 setdefaultencoding 方法
  10. sys.setdefaultencoding(‘utf-8‘)  # 设置 ‘utf-8‘
  11. # 没问题
  12. s + u  # u‘\u5173\u5173\u96ce\u9e20\u5173\u5173\u96ce\u9e20‘
  13. # 同样没问题
  14. "中文:%s" % u   # u‘\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20‘
  15. # 还是没问题
  16. u"中文:%s" % s  # u‘\u4e2d\u6587\uff1a\u5173\u5173\u96ce\u9e20‘

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

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

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

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

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example4.py
  3. import string
  4. # 这个是 str 的字符串
  5. s = ‘关关雎鸠‘
  6. # 这个是 unicode 的字符串
  7. u = u‘关关雎鸠‘
  8. # 输出 str 字符串, 显示是乱码
  9. print s   # 鍏冲叧闆庨笭
  10. # 输出 unicode 字符串,显示正确
  11. 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 环境变量保持一致:

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example5.py
  3. import sys
  4. # 检查标准输出流的编码
  5. print sys.stdout.encoding  # 设置 $LANG = zh_CN.GBK,  输出 GBK
  6. # 设置 $LANG = en_US.UTF-8,输出 UTF-8
  7. # 这个是 unicode 的字符串
  8. u = u‘关关雎鸠‘
  9. # 输出 unicode 字符串,显示正确
  10. print u  # 关关雎鸠

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

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

Python代码  

  1. python -u example5.py | more
  2. UnicodeEncodeError: ‘ascii‘ codec can‘t encode characters in position 0-3: ordinal not in range(128)
  3. None

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

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

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

Python代码  

  1. # -*- coding: utf-8 -*-
  2. # file: example6.py
  3. import os
  4. import sys
  5. import codecs
  6. # 无论如何,请用 linux 系统的当前字符集输出:
  7. if sys.stdout.encoding is None:
  8. enc = os.environ[‘LANG‘].split(‘.‘)[1]
  9. sys.stdout = codecs.getwriter(enc)(sys.stdout)  # 替换 sys.stdout
  10. # 这个是 unicode 的字符串
  11. u = u‘关关雎鸠‘
  12. # 输出 unicode 字符串,显示正确
  13. print u  # 关关雎鸠

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

Python代码  

  1. # 这个是 str 的字符串
  2. s = ‘关关雎鸠‘
  3. # 输出 str 字符串, 异常
  4. 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():

Python代码  

  1. # 使得 sys.getdefaultencoding() 的值为 ‘utf-8‘
  2. reload(sys)                      # reload 才能调用 setdefaultencoding 方法
  3. sys.setdefaultencoding(‘utf-8‘)  # 设置 ‘utf-8‘
  4. # 这个是 str 的字符串
  5. s = ‘关关雎鸠‘
  6. # 输出 str 字符串, OK
  7. print s   # 关关雎鸠

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

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

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

(完)

时间: 2024-10-13 22:51:54

001_Python2 的中文编码处理的相关文章

难道.NET Core R2连中文编码都不支持吗?

今天写了一个简单的.NET Core RC2控制台程序,发现中文显示一直是乱码.查看操作系统设置,没有问题:查看源文件编码,也没有问题:甚至查看了Console字符编码相关的注册表,依然没有发现问题.难道NET Core到了RC2,莫非连一些常用的编码都不支持吗? 现在给大家重现这个问题,通过VS 2015创建一个.NET Core控制台程序. 我们在Main方法中只编写了如下几行行程序,将输入的字符串直接打印出来. 1: using System; 2:  3: namespace App 4

深入分析 Java 中的中文编码问题 (文章来自网络)

许令波,developerWorks 中国网站最佳作者,现就职于淘宝网,是一名 Java 开发工程师.对大型互联网架构设计颇感兴趣,喜欢钻研开源框架的设计原理.有时间将学到的知识整理成文章,也喜欢记录下工作和生活中的一些思考.个人网站是:http://xulingbo.net. 为什么要编码 不知道大家有没有想过一个问题,那就是为什么要编码?我们能不能不编码?要回答这个问题必须要回到计算机是如何表示我们人类能够理解的符号的,这些符号也就是我们人类使用的语言.由于人类的语言有太多,因而表示这些语言

网上图书商城项目学习笔记-037工具类之BaseServlet及统一中文编码

1.统一中文编码分析 tomcat默认esetISO-8859-1编码,在servlet中,可能通过request的setCharacterEncoding(charset)和response.setContentType("text/html;charset=UTF-8");处理post请求编码,但get请求的编码控制不了,所以,如果请求类型是get,则用装饰者模式把request整个调包 2.EncodingFilter.java 1 package cn.itcast.filte

数据库CRUD中的中文编码问题

几天前的拼命coding碰到了很多问题,其中一个就是中国特色程序猿问题--中文编码乱码. 当我把写好的DAO模块测试CURD时,数据库执行正常,但是所有的字段中的中文全部乱码.我尝试了修改eclipse项目默认编码为UTF-8,尝试设置数据库的表的编码 alter table type character set utf8; 都无济于事. 自己曾经遇到过这种情况,但是没有整理过,到了用的时候,眉头一皱,计就是不上来. 折腾了半个钟头,终于找到了解决办法: 这里就忘了刚才的囧吧,咳咳,加入我们刚刚

Python中文编码问题(字符串前面加&#39;u&#39;)

中文编码问题是用中文的程序员经常头大的问题,在python下也是如此,那么应该怎么理解和解决python的编码问题呢? 我们要知道python内部使用的是unicode编码,而外部却要面对千奇百怪的各种编码,比如作为中国程序经常要面对的gbk,gb2312,utf8等,那这些编码是怎么转换成内部的unicode呢? 首先我们先看一下源代码文件中使用字符串的情况.源代码文件作为文本文件就必然是以某种编码形式存储代码的,python默认会认为源代码文件是asci编码,比如说代码中有一个变量赋值: s

php转换中文编码问题

用的是一个函数: function array_iconv($in_charset, $out_charset, $arr){//$in_charset代表之前的编码,$out_charset代表要转成的编码,$arr代表传过来的要转换的数组 return eval('return'.iconv($in_charset, $out_charset, var_export($arr, true))); } 解释: 先用var_export将数组$arr转换成字符串变量,然后再用iconv对该字符串

记一次UTF8中文编码的乱码

1.问题描述 业务需求  1.将某个包含中文的string转换成utf-8对应的byte[].count作为参数一起传输 2.经网络传递 3.接收传来的byte[]与其他信息 4.解码byte[] 在用utf-8解码byte[]成string时出现了尾部缺失与乱码 2.原因分析 在使用英文与数字的时候,string类型length往往与编码之后的byte[]的length一致. 但utf-8是不定长的,utf-8存储中文时占2-4个字节. utf-8是根据左侧位1的个数来决定占用了几个字节来决定

qt中文编码(好多方法)

qt中文编码 来源:http://www.cublog.cn/u1/59481/showart_1947231.html 前些日子,被编码折磨了一段时间,总结一下Qt中的编码. [Qt 编码简单实验] 首先,Qt中得QString 类对字符串进行了封装,其内部使用Unicode对传入的串进行编码.这样一来,QString就可以处理绝大多数的国际语言.将QString中的字符根据语言翻译的过程,也就是Qt 的Translater针对程序中使用含有的tr("XXXXX"),进行翻译的过程.

中文编码问题

编码的原因可以总结为: 计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个 人类要表示的符号太多,无法用一个字节来完全表示 要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码 编码格式一览 各种语言需要交流,经过翻译是必要的,那又如何来翻译呢?计算中提拱了多种翻译方式,常见的有 ASCII.ISO-8859-1.GB2312.GBK.UTF-8.UTF-16 等.它们都可以被看作为字典,它们规定了转化的规则,按照这个