Python 的编码问题笔记 (搞清原理, 一劳永逸)

近日常常 python 的编码问题纠缠的生活不能自理. 昨天终于静下心来看了看文档, 把 Python3 中的编码搞清, 用这篇文章分享记录一下(包括 utf-8 的原理).

提示:

下文中都是以 python3 为栗子🌰.
因为 python3 慢慢变成主流, 而且用 python2 的话我一般会写成兼容的模式:
>>> from __future__ import print_function, unicode_literals

编码在 python2 和 3 中的区别 ( 可跳过, 最后回过头来看 ):

摘自 Effective Python 那本书:
_
In Python3:

  1. bytes: sequences of 8-bit values.
  2. str: sequences of Unicode characters.
    bytes and str instances can’t be used with operators(like > or +)
    _
    In Python 2:
  3. str: contains sequences of 8-bit values.
  4. unicode: contains sequences of Unicode characters.
    str and unicode can be used together with operators if the str only contains 7-bit ASCII characters.
    _
    但说实话在今天前, 我对上边那段话的理解还是停留在 python3 有两种类型 (str 和 bytes) 的地步😓.

1. Python3 str 类型(unicode)

python3 的 str 字符串, 默认就代表unicode 字符组成的序列.

1
2
3
In [1]: s = '哈哈哈'   
In [2]: type(s)
Out[2]: str

那问题来了, 到底什么是 unicode 呢?
大家都知道 ASCII 编码, 它用 7 位 bits 代表 128 个字符.
但一个字节不够用的时候, 很多聪明的人就发明了很多的扩展的字符集.
可是这时候碰到了一个问题, 就是一台电脑在美利坚可能用的好好的, 但如果收到日本的邮件, 那就 GG 了, 因为两台电脑的 编码方式不同.

所有后来更聪明的人就想到了 unicode:
它对 世界上所有的字符 进行收集, 每个字符指向一个 code point(简单理解为一个唯一的数字), 这样全世界交流也不会乱码了, 棒棒哒.
所以 unicode 的一个中文名也叫 万国码.

2. Python3 bytes 类型(字节)

bytes 和 str 一样都是内置的类型:

1
2
3
In [7]: s = b'haha'
In [8]: type(s)
Out[8]: bytes

个人理解, 它代表的就是以字节 (byte) 为单位存储的二进制, i.e. 一坨的 bytes

3. Encoding/decoding:

搞清楚 python 中的 str 和 bytes 类型, 这个问题就迎刃而解了.

  1. Encoding:
    str → bytes
    因为 str 只是一堆 unicode 字符 (数字).
    所以简单的说, encoding 就是把一堆数字, 按特定的编码算法 X(例如 utf-8), 用字节的方式存储在计算机上.

  2. Decoding:
    bytes → str
    举个栗子🌰:

1
2
3
4
5
6
7
In [9]: s = '哈哈'

In [10]: s.encode('utf-8')
Out[10]: b'\xe5\x93\x88\xe5\x93\x88'

In [11]: s.encode().decode('utf-8')
Out[11]: '哈哈'

4. UTF-8 编码(encoding)

简单的说下 unicode 是如何通过 utf-8 编码转化为 bytes, 以帮助更好的理解什么是编码 (encoding).
utf-8 其实属于 动态长度编码(variable length encoding).

举个 动态长度编码简单的栗子 , 假如说有这么一个二进制序列:
10010001, 10000001, 10110010, 10110010
我们就可以利用每个 byte 的最后一位(标志位, 1 代表继续, 0 代表结束), 来判断读几个 bytes.

utf-8 也是类似的思想, 但不同于上边, 它是用每个字节 开头的几位, 当作标志位, 如下表所示:














































1st Byte 2nd Byte 3rd Byte 4th Byte 可用的 Bits 最大值
0xxxxxxx 7 007F hex (127)
110xxxxx 10xxxxxx (5+6)=11 07FF hex (2047)
1110xxxx 10xxxxxx 10xxxxxx (4+6+6)=16 FFFF hex (65535)
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (3+6+6+6)=21 10FFFF hex (1,114,111)

(生动活泼形象的编码例子见下图↓)

总结

为此我专门画了一张图, 总结了一下:

1
2
3
4
5
6
7
8
9
 
‘unicode: 01010110 00111111’
+— _str = ‘嘿’ <—+
| |
encoding | | decoding
| |
+—> _bytes = b\xe5\x98\xbf —-+
‘utf-8: (1110)0101 (10)011000 (10)11 1111’


! 注意 utf-8 编码中我用括号括起来的部分, 去对照上边的表格(第三排).

Reference: