Python编码问题

python3与python2的区别

Python2 的默认编码是 asscii,这也是导致 Python2 中经常遇到编码问题的原因之一

Python 3 默认采用了 UTF-8 作为默认编码,因此不再需要在文件顶部写 # coding=utf-8 了

# python2.7
>>> sys.getdefaultencoding()
'ascii'

# python3.5
>>> sys.getdefaultencoding()
'utf-8'

字符串

版本 字节码 字符串 默认编码
python2 str unicode ascii
python3 bytes str utf-8

py2 想定义一个日常的字符串,需要手动 u"xxx","xxx" 定义的是当前编码设定下的字节码。

# python2
>>> a="你好"
>>> type(a)
<type 'str'>
>>> isinstance(a, unicode)
False
>>> isinstance(a, bytes)
True
>>> a=u"你好"
>>> type(a)
<type 'unicode'>

在 py3 中,"xxx" 定义的就是字符串(unicode编码),b"xxx" 定义的才是字节码。

>>> a = "你好"
>>> type(a)
<class 'str'>
>>> isinstance(a, bytes)
False
>>> a = u"你好"
>>> type(a)
<class 'str'>
>>> a = b"你好"
  File "<stdin>", line 1
SyntaxError: bytes can only contain ASCII literal characters.
>>> a = b"secret"
>>> type(a)
<class 'bytes'>

所以在 py3 中,coding: utf-8 的注释没有任何用处了。在 py2 中它会隐式的根据指定编码对 str 做字节码的转换,但在 py3 中已经没有这种隐式的转换和定义了。

py2编码问题的坑

在 py2 中经常遇到的错误:

if __name__ == "__main__":
    a="你好"
#  File "test_py2.py", line 2
# SyntaxError: Non-ASCII character 'xe4' in file test_py2.py on line 2, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

在py2中会隐式地尝试使用默认编码方式(ascii)将 "你好" 编码为字节码,而ascii编码无法对中文编码,因此报错

# coding=utf-8

if __name__ == "__main__":
    "你好".encode('utf-8')

# Traceback (most recent call last):
#   File "test_py2.py", line 4, in <module>
#    "你好".encode('utf-8')
# UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)

首先我们已经知道py2中默认字符串为字节码,那么"你好"应该是一个utf-8编码方式(文件定义的编码方式)生成的字节码,怎么能对字节码再进行编码呢?

因为 py 以 unicode 作为运算基准,所以在执行编码操作前会先隐性地将字节码 decode 至 unicode,并使用默认字符集(并非文件定义的编码方式)ascii做解码,因此就无法解码utf-8编码形成的字节码

而在 py3 中是不允许这种操作的。从 bytes 到另一 bytes 你必须先 decode 后才能 encode。

Unicode与字节码

Unicode是什么

Unicode的发明目的是让世界上的每一个符号都有一个独一无二的编码

Unicode 是一个符号集,它规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

为什么不直接存储Unicode编码?

符号的Unicode编码有的长有的短。

比如 'a' 的unicode编码是7位,一个字节就能存下(与ascii编码相同)

>>> ord("a")
97 // Unicode编码的十进制表示法
>>> hex(ord("a"))
'0x61'  // 转成16进制就是我们通看到的Unicode编码格式
>>> bin(ord("a"))
'0b1100001' // 转成二进制,可以看到 "a" 的Unicode编码有7位,一个字节

而汉字的unicode编码通常需要两个字节才能存下

>>> ord("严")
20005
>>> hex(ord("严"))
'0x4e25'
>>> bin(ord("严"))
'0b100111000100101' // “严”的unicode编码有15位,需要两个字节才能存下

计算机并不知道几个字节表示一个符号,这就给存储和读取带来了不便。如果统一用两个字节存储一个符号,那么对于英文字符来说,前一个字节就全部是0了,这样是对存储空间的浪费。

因此,需要一种聪明的方法来存储Unicode编码,既不要浪费存储空间,也要让机器能够分辨几个字节表示一个符号

UTF-8编码

UTF-8编码是Unicode的一种实现方式,它将Unicod编码分为四个范围,用1-4个字节表示一个字符,即:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

Unicode符号范围 (十六进制) UTF-8编码方式(二进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,计算机解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字严为例,演示如何实现 UTF-8 编码。

严的 Unicode 是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此严的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从严的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,严的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。

>>> "严".encode("utf-8")
b'xe4xb8xa5'
>>> for b in "严".encode("utf-8"):
...     print(type(b), b, bin(b))
... 
# 字符串的元素是字符,bytes对象的元素则是字节
# 字节对象本质上是一个 0 <= x < 256 区间内的整数不可变序列
# 我们可以用方括号来取得每个字节
# 然后,用bin(b)来直观地看每个字节在内存中的存储方式
<class 'int'> 228 0b11100100
<class 'int'> 184 0b10111000
<class 'int'> 165 0b10100101

参考资料

python2与python3字符串的区别

字符编码笔记:ASCII,Unicode 和 UTF-8

Python3的字节类型(bytes)

字体编辑用中日韩汉字Unicode编码表

ASCII码对照表

原文地址:https://www.cnblogs.com/luozx207/p/13129770.html