一文说清楚如何处理 Python 的编码
本文最后更新于:2 年前
用 Python 久了,总会遇到与中文编码相关的问题,不管是打印的时候出现乱码,还是直接报错。而我之前一直是不断使用 encode、decode 进行尝试,今天实在受不了了,彻底摸清楚了,遂整理了一番。
编码种类以及起源
自行了解
从 python 2.x 说起
str 与 unicode
什么是 str,什么是 unicode
首先,str 和 unicode 都是 basestring 的子类。
那么,我们怎么知道一个变量里到底是 str 还是 unicode 呢?
Python 提供了 isinstance 函数:
- 判断
string是否为str:isinstance(string, str) - 判断
string是否为unicode:isinstance(string, unicode)
以及 type 函数:
1
2
3
4
5In [3]: type("中文")
Out[3]: str
In [4]: type(u"中文")
Out[4]: unicode
那么,我们常说的 字符串,到底哪个呢?我认为,看个人。如果你和我一样觉得直接写在源代码里的(如 a = "123")就叫字符串,那么 str 就是字符串。如果你觉得 字符串 是由 多个字符 串起来 的,那么 unicode 才是字符串。
也就是说,unicode 由 多个字符 组成,比如:
1
2
3
4
5In [1]: len(u'中文')
Out[1]: 2
In [2]: len('中文')
Out[2]: 6
而 '中文' 由 中 与 文 这两个字符组成。明显 unicode 更符合我们求长度的逻辑。
总结:不管怎么样,对字符串的定义,选一个自己认为合理的,这个无关紧要。我倾向于前者,所以下面默认 字符串 就是 str。
创造 str 与 unicode
直接创造 str 与 unicode 的方法都很简单,直接写在源码里:
- str:
a = "123" - unicode:
a = u"123"
还有那些地方会创造出 str 与 unicode 呢?比如读/写文件,从远程获取(爬虫或 socket)等等。
str2unicode,unicode2str
Python 提供了 decode(解码)/encode(编码) 用于两者的转换:
- str -> decode("某种编码") -> unicode
- unicode -> encode("某种编码") -> str
总结:unicode 经过编码后形成 str,str 经过解码后形成 unicode
至于如何选择正确的 "某种编码",下面会说。
如何正确地编码/解码
环境编码
系统默认编码
- Linux,Unix:utf8
- Windows:gbk
还有输出时,打印到终端的编码
编辑器的编码
不管是 IDE 还是 atom 记事本,打开源代码的时候总会按照某一特定的编码打开。基本上,这个编码总是可以设置的,如 atom:

保证源代码的文件编码,为避免乱码以及后续处理的方便,最好统一设置为 utf8
文件头部编码声明
即 python 解释器读取源代码时的编码。
python 解释器读取源代码时默认编码是 ASCII。在源代码文件中,如果用到非 ASCII 字符(如中文),需要在文件头部进行编码声明。否则会报错:
1
2File "test.py", line 8
SyntaxError: Non-ASCII character '\xe6' in file test.py on line 9, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
声明编码方式为:# -*- coding: utf-8 -*- 或 #coding=utf-8(-*- 实际上没啥用,只是为了...好看...)。对 Python 来说,无所谓你写的是 utf-8 还是 utf8,它都能认识。
注意,声明编码必须放在源代码的第一行
假设代码中有:a = '中文'。若头部声明 utf-8, 则 a 的编码类型为 utf-8;若头部声明 gbk,则 a 的编码类型为 gbk。即,代码中直接声明的字符串编码类型,与此直接相关。
所以,为了避免乱码以及后续处理的方便,最好统一头部声明为 utf8
如何选择编码类型
现在我们知道了:常见编码类型有: ascii, utf8, gbk。
先做几个小实验:
- 文件头部声明对字符串编码类型的影响(假设声明为
utf8): # -*- coding: utf8 -*- -> "任意字符串" -> utf8# -*- coding: utf8 -*- -> u"任意字符串" -> unicode- unicode 的 encode/decode:
纯 ascii 的 unicode -> encode("编码类型 A") -> 字符串含中文的 unicode -> encode("任意编码类型") -> 字符串纯 ascii 的 unicode -> decode("任意编码类型") -> unicode含中文的 unicode -> decode("任意编码类型") -> 'ascii' codec can't encode characters in position 1-32: ordinal not in range(128)- 字符串的 encode/decode:
纯 ascii 的字符串 -> decode("任意编码类型") -> unicode含中文的字符串 -> decode("utf8") -> unicode含中文的字符串 -> decode("非 utf8") -> 乱码 或 UnicodeDecodeError: '非 utf8' codec can't decode bytes in position 117-118: illegal multibyte sequence纯 ascii 的字符串 -> encode("任意编码类型") -> 字符串含中文的字符串 -> encode("任意编码类型") -> UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 115: ordinal not in range(128)
文件头部声明为 gbk 时同理。
于是,我们可以得出结论:
- 除非有信心代码不会出现中文(包括硬编码或者数据流)之外,头部一定要声明编码,且最好为
utf8。 ascii编码类型,随便你怎么搞都没事。- 最好,不要对
str使用encode,不要对unicode使用decode。 - 编码之间的转换怎么处理呢?用 unicode 搭桥
1
2
3
4
5# -*- coding: utf8 -*-
string = "我"
print string.decode('utf8').encode('gbk')
# 先解码为 unicode,再编码为 gbk
# 在终端字符设置为 utf8 时,输出为乱码
网上的一些说法
利用 sys 解决。方法如下:
在源代码处添加:
1
2
3import sys
reload(sys)
sys.setdefaultencoding('utf-8')
那么,这个方法的原理是什么呢?
在 python 中,对 str 使用 encode 时,其实是先解码为 unicode ,然后再编码的。但是,这个时候我们并没有声明编码类型,于是 Python 就用默认的编码类型:ascii。改变这一默认编码类型的方式就是上面的那段代码。
我们还可以做个小实验
1 | |
显然这个方法并不彻底,说不定还会造成奇怪的 bug。所以最好别用。
不过话说回来,这个自动编码,有时候真是大坑:
1 | |
也难怪很多人用这个方法了吧。。。
阶段总结
py2.x:
- 规范编码:环境编码 + IDE/文本编辑器 + 文件编码 + 数据库编码+...
- 头部编码声明
- 不要对
str使用encode,不要对unicode使用decode - 乱码/报错的时候,先搞清楚编码的类型,查找小实验中对应的情况,再进行对应的编码/解码
- 强烈建议统一用 utf8,不管网站也好,数据库也好,这是大势所趋。
痛改前非的 python 3.x
字符串?str?unicode?bytes?
py2.x 的非 ascii 编码/解码问题一直被吐槽这么多年后,py3.x 终于下定决心彻底解决这个问题。解决的方式:
文本总是
Unicode,由str类型表示,二进制数据则由bytes类型表示。Python 3 不会以任意隐式的方式混用str和bytes
所以,py3.x 首先统一了表示形式,然后不会再任意隐式的方式混用。
接下来解释一下:首先,Unicode 是内存编码表示方案(是规范),而 UTF 是如何保存和传输 Unicode 的方案(是实现)。 这也是 UTF 与 Unicode 的区别。py3.X 中只有一种能保存文本信息的数据类型:str,不可变,保存的是 Unicode 码位。Unicode 是离用户更近的数据,bytes 是离计算机更近的数据。
在 py2.x 中,我们曾经讨论过 什么是 str,什么是 unicode,以及字符串的定义问题,还举了一个例子,说明 unicode 在求长度的时候更符合逻辑。所以,py3.x 认为 unicode 才是 字符串,而在 Py2.x 中的 str,更像是 py3.x 中的 bytes。
说得有点多,总结一下:
- py2.x
字符串:str类型type("123"):strlen("我"):3len("我".decode("utf8")):1
- py3.x
字符串:unicode类型type("123"):strlen("我"):1len("我".encode()):3
- 注意,
str表示的概念都是字符串类型,只不过 Py2.x 的 str 是经过编码的 unicode,而 py3.x 的 str 就是 unicode。 那么,py3.x 的 unicode 经过编码后,是什么呢?就是bytes。也就是说,py3.x 的字符串默认是 unicode,至于你想把它怎么编码(utf8 或者 gbk),都是你自己的事。
说清楚 py3.x 的解决方案之后,我们再来看它的编码/解码。
编码/解码
对于 str 类型(即 unicode),只有一个 encode 方法将 字符串 转化为一个字节码(即 bytes),而且 bytes 也只有一个 decode 方法将字节码转化为一个文本字符串。
1 | |
基本不会有乱码以及各种奇怪报错的问题了。算是彻底解决了。
阶段总结
py3.x:
- 规范编码:环境编码 + IDE/文本编辑器 + 文件编码 + 数据库编码+...
- 头部编码声明:不再必要
- 乱码/报错的时候,一定是解码的时候,解码的编码类型与原有编码类型不匹配。
谈谈兼容性
py2.x 与 3.x 如何进行正确地编码/解码都说好了,现在说说兼容性。回顾 py3.x 的解决方案,我们可以获得启示:默认使用 unicode,需要的时候再编码。
Python 作出的努力
自从 py3.x 出世,以及官方声明不在对 2.x 进行更新后,兼容性一直是一个大问题。
对此,最新的 py2.7.15,支持 py3.x 的一些语法;而 3.x 也保留了一些 py2.x 的语法:
- py2.x:
a = b"123"# a 依然是 str 而不是 bytes。- 可以进行头部编码声明
- py3.x:
a = u"123"# a 本来就是 unicode- 可以进行头部编码声明
以上语法仅仅是为了兼容,不会起到它们原来的作用。
我们需要注意的地方
兼容性靠别人不如靠自己,写代码的时候不妨留意一点编码问题。
- 规范编码:环境编码 + IDE/文本编辑器 + 文件编码 + 数据库编码+...
- 声明文件头部编码
- 硬编码的时候加上
u:a = u"我" - 将 Python 代码当做一个杯子,将数据流倒进杯子的时候,一律解码转为 unicode。杯子里一律用 unicode 处理。将数据流倒出杯子的时候,编码为 utf8(当然,看具体使用场景)。
- 始终坚持使用 UTF-8 编码,始终坚持使用 UTF-8 编码,始终坚持使用 UTF-8 编码
来呀快活呀