以下内容转载自:

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

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

很快,我就遇到了异常:

UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal notin 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的编码处理(一)的更多相关文章

  1. python字符串编码理解(转载)

    (转载)字符编码和python使用encode,decode转换utf-8, gbk, gb2312 (http://www.cnblogs.com/jxzheng/p/5186490.html) A ...

  2. python的编码问题

    本文简单介绍了各种常用的字符编码的特点,并介绍了在python2.x中如何与编码问题作战 :) 请注意本文关于Python的内容仅适用于2.x,3.x中str和unicode有翻天覆地的变化,请查阅其 ...

  3. Python字符编码详解

    1. 字符编码简介 1.1. ASCII ASCII(American Standard Code for Information Interchange),是一种单字节的编码.计算机世界里一开始只有 ...

  4. Python字符编码以及循环机制介绍

    Python字符编码以及循环机制介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 身为一名运维人员就得不断的学习,那么如何身为运维的你是否已经嗅探已经很火的Python编程啦?大 ...

  5. 【安全开发】python安全编码规范

    申明:本文非笔者原创,原文转载自:https://github.com/SecurityPaper/SecurityPaper-web/blob/master/_posts/2.SDL%E8%A7%8 ...

  6. Python字符编码详解,str,bytes

    什么是明文 “明文”是可以是文本,音乐,可以编码成mp3文件.明文可以是图像的,可以编码为gif.png或jpg文件.明文是电影的,可以编码成wmv文件.不一而足. 什么是编码?把明文变成计算机语言 ...

  7. [转载] Python数据类型知识点全解

    [转载] Python数据类型知识点全解 1.字符串 字符串常用功能 name = 'derek' print(name.capitalize()) #首字母大写 Derek print(name.c ...

  8. [转载]Python 资源大全中文版

    [转载]Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-python 是 vinta 发起维护的 Python ...

  9. python Unicode 编码解码

    1 #将Unicode转换成普通的Python字符串:"编码(encode)" 2 unicodestring = u"Hello world" 3 utf8s ...

  10. [转载]Python 资源大全

    原文链接:Python 资源大全 环境管理 管理 Python 版本和环境的工具 p – 非常简单的交互式 python 版本管理工具. pyenv – 简单的 Python 版本管理工具. Vex  ...

随机推荐

  1. centos 关闭selinux 临时关闭selinux 报错 setenforce: setenforce() failed

    关闭selinux的方法有两种:临时关闭和永久关闭. 查看selinux的状态:estatus [root@--- ~]# sestatus SELinux status: enabled SELin ...

  2. Java接口调用工具类

    package com.qiyuan.util; import java.io.BufferedReader; import java.io.IOException; import java.io.I ...

  3. no awt in java.libary.path

    缺少依赖包. linux下进入jdk安装路径/jre/lib/i386 使用ldd查看依赖包情况: ldd libawt.so 如果发现有not found的,就安装上,或者从jdk下查找,使用软连接 ...

  4. SpringMVC源码阅读:核心分发器DispatcherServlet

    1.前言 SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧 本文将介绍SpringMVC的核 ...

  5. FSM有限状态机

    1.什么是有限状态机 有限状态机(Finite State Machine),简称FSM,它由一组有限个状态.输入和根据输入及现有状态转换为下一个状态的转换函数组成,当然,通常每个状态机都必须有一个初 ...

  6. 常用算法3 - 字符串查找/模式匹配算法(BF & KMP算法)

    相信我们都有在linux下查找文本内容的经历,比如当我们使用vim查找文本文件中的某个字或者某段话时,Linux很快做出反应并给出相应结果,特别方便快捷! 那么,我们有木有想过linux是如何在浩如烟 ...

  7. vs2013 sn key

    BWG7X-J98B3-W34RT-33B3R-JVYW9  

  8. Deep Q-Network 学习笔记(五)—— 改进③:Prioritized Replay 算法

    也就是优先采样,这里的推导部分完全没看懂 Orz,这里也只是记录实现代码. 也就是看了以下两篇文章对应做了实现. 莫烦老师的教程: https://morvanzhou.github.io/tutor ...

  9. ORACLEserver实例DB的概念学习理解与总结【进阶一】

    个人原创,转自请在文章开头显眼位置注明出处:https://www.cnblogs.com/sunshine5683/p/10048824.html 一.以后看一个oracleserver,可以使用如 ...

  10. FUzhou 1607 Greedy division---因子个数问题。

    Problem 1607 Greedy division http://acm.fzu.edu.cn/problem.php?pid=1607 Accept: 402    Submit: 1463T ...