【转载】python2与python3字符串的区别

原文出处:https://my.oschina.net/sallency/blog/1563298

python2

在 py2 中,有两种类型字符串:str 和 unicode。但严格的来说,str 并不是完全意义上的字符串,把它称作 字节码串更合适。unicode 则作为真正意义上的 字符串,但定义时需要使用 u"" 去声明。有人可能会有疑问,字符串 在内存单元中也是以字节进行存储的,怎么能将他俩区别对待呢?

字符串 是一种 数据结构。数据结构 = 原数据 + 数据描述(为了更容易理解),而字符串的 “数据描述” 即为何种字符集。

字节码 则是没有数据描述的。

py2 中,当我们读取 str 类型时,它只代表了一串数据,至于这串数据是何种编码,系统是不知道的,需要我们手动指定。而 unicode 类型,则存储的相应的字符集,系统在读取时便能根据此数据描述对数据进行识别和处理。

'\xc4\xe3\xba\xc3' 对计算机来说,就是 1 个字节的字节码串在一起。

u'\u4f60\u597d' 对计算机来说,则是每 2 个字节作为一个单位,去映射 unicode 字符集做运算。

66433-510e2d5785be0e1f.png
image

当我们定义 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 字符集的字节码:

66433-cb11661fe66e04b0.png
image

encode/decode 下面详细讲解。

python3

py3 则对字符串和字节码进行了更为规范的定义:str 和 bytes。str 终于成为了真正意义上的字符串,bytes 也形象的表征了字节码串儿(java 中的字节数组)

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")

encode/decode

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。

open/read/write

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 貌似已经没什么用处了

猜你喜欢

转载自blog.csdn.net/weixin_33834628/article/details/87254099