一文说清楚如何处理 Python 的编码
用 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")
:str
len("我")
:3len("我".decode("utf8"))
:1
- py3.x
字符串
:unicode
类型type("123")
:str
len("我")
: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 编码
来呀快活呀