Python2的神奇编码

尝试解决Python编码问题

基础知识

编码基础

  1. Unicode是一种字符标准,GBK、UTF-8都是其实现,我们说的编码应当是后者(即具体的实现)
  2. Windows默认编码为GBK,Linux/MacOS为UTF-8,这会影响python文件IO,其实不影响本身的编码
  3. Python有默认字符类型和默认字节类型
  4. 底层存储用的是二进制(字节类型序列),使用什么编码来识别它决定了它显示为乱码还是期望值

decode/encode方法

不论在python2还是python3,encode即转换为字节类型,decode即转换为字符串类型,只不过两个版本的python对字节类型、字符串类型做了修改

那么,理论上字节类型只应该有decode方法,字符串类型只应该有encode方法才对吧(python3是这样的)?

但是由于python2的设计缺陷,即两者同时有两种方法

另外,在Python2中,两个方法的默认参数为encoding=‘ascii’,而python3中为utf-8,这取决于python编码,即sys.getdefaultencoding()

Python2情形

str字节类型

在使用引号引住的情形,默认使用str类型,这是已经编码过的字节序列,即bytes

1
2
3
4
5
6
>>> type('a')
<type 'str'>
>>> type('哈哈')
<type 'str'>
>>> type('😑')
<type 'str'>

字节序列的特点是\x ,比如

1
2
>>> '哈哈'
'\xe5\x93\x88\xe5\x93\x88'

这里\x是表示其为字节序列,即直接说明了16进制串为 e59388e59388,这里可以看到被编码为了16进制串e59388

这说明在Python2、MacOS下,默认一个汉字占用3字节,实际上,这是因为一个中文字被UTF-8编码为了3字节

验证一下’哈哈’是什么编码

1
2
>>> chardet.detect('哈哈')
{'confidence': 0.7525, 'language': '', 'encoding': 'utf-8'}

在此要额外说一下

1
2
3
4
>>> '\xe5\x93\x88\xe5\x93\x88'.decode('utf-8')
u'\u54c8\u54c8'
>>> print(u'\u54c8\u54c8')
哈哈

而python3中,bytes是没有decode方法的,python2发什么了什么呢?

python2的str类型也有encode方法,这是设计缺陷

另外我们在Windows、Python2下尝试一下

1
2
>>> '哈哈'
'\xb9\xfe\xb9\xfe'

和MacOS情形不同,但是同样是哈哈,为什么显示的结果不同呢?

这就是bytes,简单来说,bytes是存储在物理设备上的实际值,至于怎么存、怎么理解它,取决于UTF-8、GBK这样的实现标准

当然我们也可以手动确认使用什么标准,例如

1
2
>>> print('\xb9\xfe\xb9\xfe'.decode('gbk'))
哈哈

unicode字符串类型

1
2
3
4
>>> type(u'哈哈')
<type 'unicode'>
>>> type(u'a')
<type 'unicode'>

上面说过,unicode是一种标准,这里python2将unicode作为一种字符串类型,可以看做一种实现

在MacOS、Windows上,下面的返回是相同的

1
2
3
4
>>> u'哈哈'
u'\u54c8\u54c8'
>>> type(u'哈哈')
<type 'unicode'>

在字符串前面的\u表示其后面为unicode字符串,由于其本身是一种实现,那么在Windows、MacOS上就不会有区别,这点与str类型不同

Python3 情形

bytes字节类型

在Python3中引入了b’'表示字节类型,且type使用了bytes

1
2
3
4
>>> b'\xe5\x93\x88\xe5\x93\x88'
b'\xe5\x93\x88\xe5\x93\x88'
>>> type(b'\xe5\x93\x88\xe5\x93\x88')
<class 'bytes'>

并且python3不允许字节、字符串类型隐式转换,必须使用encoded/decode方式,由此很多库的返回值要多加一个decode了

str字符串类型

1
2
3
4
5
>>> '哈哈'
'哈哈'
>>> type('哈哈')
<class 'str'>

python3使用了str,即引号引住的方式表示这是一个字符串,也不需要使用\u来特意声明

Python2的编码问题

有一个核心的误导在于,str和unicode是两种不同的数据类型,但是偏偏可以混用(python2的str和unicode同是一个父类basestring

而混用时隐式转换使用的是环境默认编码(ascii)

在python2中

1
2
3
4
>>> base64.b64encode(u'abc')
'YWJj'
>>> base64.b64encode('abc')
'YWJj'

在python3中

1
2
3
4
5
6
7
8
>>> base64.b64encode('abc')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/base64.py", line 58, in b64encode
encoded = binascii.b2a_base64(s, newline=False)
TypeError: a bytes-like object is required, not 'str'
>>> base64.b64encode(b'abc')
b'YWJj'

这里是python2的unicode向str隐式转换,下面这个例子更好理解

1
2
3
4
>>> 'abc' + u'abc'
u'abcabc'
>>> type('abc' + u'abc')
<type 'unicode'>

而python3中这样是不允许的

1
2
3
4
>>> 'abc' + b'abc'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: must be str, not bytes

其次,python存在默认的编码和参数

在python2中,encode、decode的参数为ascii,这导致了只有寥寥字母数字可以顺利通关,其他的不指定编码必定报错

1
2
3
4
5
6
>>> u'abc'.encode()
'abc'
>>> u'我要报错'.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-3: ordinal not in range(128)

这里的错误时说,它没法用ascii识别这个中文的utf-8编码

因为字节是无国界的,编码实现是有偏见的,这里想要把unicode转为str,而encode默认使用ascii方式来转换,

但是这里是中文,unicode->ascii转换失败(越界)

1
2
3
4
5
6
>>> u'abc啊'.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\u554a' in position 3: ordinal not in range(128)
>>> u'\u0061\u0062\u0063啊'
u'abc\u554a'

暴力的解决方案

不能使用Python3的情况下,统一编码是你唯一的出路,最好加上文件保存编码声明 # -*- coding: utf-8 -*-

强烈反对使用sys.setdefaultencoding(),因为几乎所有问题都可以通过encode/decode解决,且Dangers of sys.setdefaultencoding(‘utf-8’)

首先明确进入的字符串是什么编码,使用这个编码将之encode为字节类型,就可以随意转换了,为了以后兼容,使用UTF-8是一种好方案

一般原则

文本使用字符串类型,通信、网络、IO接口使用字节类型

最近的文章

python解析js方案

在Python爬虫的时候遇到的JS,几种策略 …

于  python 继续阅读
更早的文章

Sender部署

¶RabbitMQ rpm -Uvh http://www.rabbitmq.com/releases/erlang/erlang-18.1-1.el7.centos.x86_64.rpm rpm -Uvh http://www.rabbitmq.com/releases/rabbitmq-ser …

于  Django, Nginx, Vue, centos, nodejs, python, uWSGI 继续阅读