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)
>>> isinstance(a, unicode)
False
>>> isinstance(a, bytes)
True
>>> a=u"你好"
>>> type(a)
在 py3 中,"xxx" 定义的就是字符串(unicode编码),b"xxx" 定义的才是字节码。
>>> a = "你好"
>>> type(a)
>>> isinstance(a, bytes)
False
>>> a = u"你好"
>>> type(a)
>>> a = b"你好"
File "", line 1
SyntaxError: bytes can only contain ASCII literal characters.
>>> a = b"secret"
>>> type(a)
所以在 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
# "你好".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'\xe4\xb8\xa5'
>>> for b in "严".encode("utf-8"):
... print(type(b), b, bin(b))
...
# 字符串的元素是字符,bytes对象的元素则是字节
# 字节对象本质上是一个 0 <= x < 256 区间内的整数不可变序列
# 我们可以用方括号来取得每个字节
# 然后,用bin(b)来直观地看每个字节在内存中的存储方式
228 0b11100100
184 0b10111000
165 0b10100101