在 py2 中,有两种类型字符串:str 和 unicode。但严格的来讲,str 并非彻底意义上的字符串,把它称做 字节码串 更合适。unicode 则做为真正意义上的 字符串,但定义时须要使用 u"" 去声明。有人可能会有疑问,字符串 在内存单元中也是以字节进行存储的,怎么能将他俩区别对待呢?java
字符串 是一种 数据结构。数据结构 = 原数据 + 数据描述(为了更容易理解),而字符串的 “数据描述” 即为什么种字符集。python
字节码 则是没有数据描述的。数组
py2 中,当咱们读取 str 类型时,它只表明了一串数据,至于这串数据是何种编码,系统是不知道的,须要咱们手动指定。而 unicode 类型,则存储的相应的字符集,系统在读取时便能根据此数据描述对数据进行识别和处理。数据结构
'\xc4\xe3\xba\xc3' 对计算机来讲,就是 1 个字节的字节码串在一块儿。编码
u'\u4f60\u597d' 对计算机来讲,则是每 2 个字节做为一个单位,去映射 unicode 字符集作运算。spa
当咱们定义 str字符串 时,python 会根据咱们当前的运行上下文:源文件 / cli 去设定 str 的字符集。好比咱们在脚本开始处注释的 coding: utf-8 会将脚本中的 str 编码为 utf-8 的字节码串儿。操作系统
# -*- coding: utf-8 -*- import sys """ 做用是注明脚本上下文的字符集 好比当咱们定义一个 str 类型的数据时 它是 utf-8 字符集的字节码 """ str = "你好" #以 utf-8 字节码去编码 """ 但不要和 python 的默认字符集搞混 默认字符集会在一些隐式加解码时被运用 """ # ascii print sys.getdefaultencoding() # 设置 python 的默认字符集 reload(sys) sys.setdefaultencoding("utf-8") """ 下面的代码存在一次隐式的 decode 在隐式或没有指定 decode 的字符集时 会使用 默认字符集(这里已将默认字符集设为 utf-8)进行解码 若是咱们没有修改默认字符集而使用 ascii 的话 就会报错了 ascii 是没办法编码中文的 """ "你好".encode("gb2312") """ py2中 write 写的为字节码串 当前源码编码字符集为 utf-8 因此写入时 "你好" 是以 utf-8 编码的 """ with open("encode.txt", "w") as f: f.write("你好") """ 读取的 str 也为 utf-8 的字节码串 """ with open("encode.txt", "r") as f: str = f.read()
例如个人 cmd 是 gb2312 字符集,因此 "你好" 的字节码串以下(gb2312 一个中文字符用 2 个字节标识)。下面咱们将其转换为 utf-8 字符集的字节码:code
encode/decode 下面详细讲解。内存
py3 则对字符串和字节码进行了更为规范的定义:str 和 bytes。str 终于成为了真正意义上的字符串,bytes 也形象的表征了字节码串儿(java 中的字节数组)utf-8
py3 默认支持 unicode 字符集,故不须要鸡肋的使用 py2 中的 u"xxx" 去定义一个 unicode 字符串。
简单来讲,py2 想定义一个平常的字符串,须要手动 u"xxx","xxx" 定义的是当前编码设定下的字节码串儿,典型的本末倒置。在 py3 中,再也不有 u"xxx" 的语法,"xxx" 定义的就是字符串(unicode编码),b"xxx" 定义的才是字节码串儿。
""" py3 中消除了py2中字节码和字符串混乱的定义 str 就是字符串 bytes 才是字节码 在 py2 中 str 是字节码 unicode 才是字符串 """ py3 py2 字节码 bytes str 字符串 str unicode
因此在 py3 中,你的 coding: utf-8 的注释没有任何用处了。在 py2 中它会隐式的根据指定编码对 str 作字节码的定义,但在 py3 中已经没有这种隐式的转换和定义了。
import sys # 默认字符集由 ascii 改成 utf-8 print("默认字符集为:%s" % (sys.getdefaultencoding())) str = "你好" # 2 个字符长度 py2 中就不会把 str 看为数据结构 而是字节码 因此结果会是 6 print(len(str)) # 不指定字符集将使用默认的 utf-8 进行编码 str.encode() # b'\xe4\xbd\xa0\xe5\xa5\xbd' str.encode("utf-8") # b'\xe4\xbd\xa0\xe5\xa5\xbd' str.encode("gb2312") # b'\xc4\xe3\xba\xc3' hello_utf8 = b'\xe4\xbd\xa0\xe5\xa5\xbd' hello_gb2312 = b'\xc4\xe3\xba\xc3' # utf-8 转 gb2312 / gb2312 转 utf-8 # 都是以 unicode 做为基准进行操做 hello_utf8.decode("utf-8").encode("gb2312") hello_gb2312.decode("gb2312").encode("utf-8")
py 以 unicode 做为字符运算的基准。
bytes -- decode -- unicode -- encode -- bytes
unicode 编码 至其余字符集(utf-8/gb2312),其余字符集 解码 至 unicode
在 py2 中常常遇到的错误:
"你好".encode("utf-8")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 0: ordinal not in range(128)
错误的缘由是什么呢?py2 以 ascii 做为默认字符集,"你好" 直接 encode 至其余编码时会进行一次隐式的解码。由于 py 以 unicode 做为运算基准,因此会先 decode 至 unicode,你没使用 py 就隐式执行,使用默认字符集 ascii 作解码,天然没法解码中文字符的字节码。
而在 py3 中是不容许这种操做的。从 bytes 到另外一 bytes 你必须先 decode 后才能 encode。
py2 中,open 方法并不会设定以何种字符集去打开文件,由于 py 要的不是字符串,而是字节码串儿。是的,read 和 write 操做的都是字节码串儿,字节码串儿自己并不具有表征字符的能力,咱们须要手动的指定正确的字符集进行解码。
# write.py # -*- coding: utf-8 -*- with open("hello.txt", "w") as f: f.write("你好") # 这里实际上是字节码 # read.py # -*- coding: gbk -*- # 结果为 6 仍然是以 utf-8 的字节码去计算的 with open("hello.txt", "r") as f: str = f.read() print len(str) # 读取的也是字节码
数据会以相应的字节码去保存
py3 中,由于存储时传入的是 unicode 字符串(而再也不是 py2 的字节码),但数据存储确定是以字节码的形式(物理存储)。因此 py3 确定会将字符串转为字节码(这个过程是py3本身完成的,咱们此时但是指定以何种字符集去编码),这时就涉及采用何种编码去进行隐式的 encode 存储和 decode 读取了。
中文系统,使用 python xxx.py 时上下文的编码为操做系统的编码:gbk。这里挺饶的,虽然我是在 gbk2312 的 cmd 里运行的,但并不是是交互模式,因此输入时并不是使用的 cmd 的字符集,而是 python 解释器启动时读取的本地系统的字符集:gbk。
#-*-coding: utf-8 -*- """ py3 的默认字符集为 utf-8 因此平常开发中咱们是能够省略 encoding 的 """ with open("hello.txt", "w", encoding = "utf-8") as f: f.write("你好") with open("hello.txt", "r", encoding = "utf-8") as f: print(f.read()) # -*-coding: utf-8 -*- 就算我声明了此行注释 # py3 依然不会使用其做为 open 打开文件时默认的 encoding # 也不会使用 py3 自身默认的 sys.getdefaultencoding # 这里 open 的 encoding 默认会与你的本地系统相关 # 因此下面我须要使用 gbk 返回字节码,从新使用 utf-8 正确解码 with open("hello.txt", "r") as f: print(f.read().encode("gbk").decode('utf-8')) # # 以 b(二进制) 方式读取时为读取的字节码串儿 # 因此数据并不会受编码影响 # 咱们使用时须要指定正确的解码字符集 with open("hello.txt", "rb") as f: bytes = f.read() print(bytes) print(len(bytes)) # 6 utf-8 的字节码 一个中文3个字节 print(bytes.decode("utf-8")) #正常解码 得到 "你好" unicode 字符串 # bytes.decode("gb2312") #错误解码
因此,coding: utf-8 貌似已经没什么用处了