Python3中bytes和str的区别

131 阅读5分钟

笔者去年录制视频《Python3中bytes和str的区别》之前,首先输出过一篇博客来理一理计算机中与“编码”相关的内容:

  • 人类的沟通交流,以文字为媒介;
  • 计算机为人类服务,它所作的一切都是为将人类的信息进行加工并展示,其中最重要的一个元素是文字;
  • 为了所有文字都能在计算机当中展现,科学家们为各种各样的文字做了一张表,每一个文字——不管是英文字母、汉字、韩文——都有属于自己的唯一编号,这些编号汇总起来叫做Unicode;
  • 计算机的存储资源很宝贵,这些编号都需要存储在计算机中,为了节省存储空间,于是有不同的存储方式,这些存储方式叫做UTF-8、GBK、甚至ASCII,即只存自己需要的内容。

理清楚这些区别之后,笔者再阅读书中内容,就多出一种了然于心之感。(笔者此处是推荐所有程序员都看一些类似编码这样比较基础的计算机知识的,理清楚设计初衷后,学新知识会快许多。当然,此处笔者也对自己说。)

笔者此处有一个小小的测试以说明Python3中bytes和str的转换:

Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> s = '中国'  # str类型的字符串
>>> len(s)
2
>>> b = s.encode('utf-8')  # 将str按照utf-8格式编码为bytes
>>> b
b'\xe4\xb8\xad\xe5\x9b\xbd'
>>> len(b)
6
>>> list(b)  # bytes是一个个字节拼凑起来的
[228, 184, 173, 229, 155, 189]
>>>
>>> for v in list(b): print(bin(v))  # utf-8中3个字节表示汉字“中”与“国”
...
0b11100100
0b10111000
0b10101101
0b11100101
0b10011011
0b10111101
>>>
>>> b2 = s.encode('gbk')  # gbk中汉字的表示,只需要2个字节
>>> list(b2)
[214, 208, 185, 250]
>>> for v in list(b2): print(bin(v))
...
0b11010110
0b11010000
0b10111001
0b11111010
>>> b2
b'\xd6\xd0\xb9\xfa'
>>>
>>> b3 = s.encode('ascii')  # 汉字不能编码进ASCII码表区间
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)

笔者搞清楚编码之后,感觉本小节内容是比较简单的。于是此处只将本小节重点内容做一个复述:

  • bytes和str都是序列,bytes当中存储的是8位值,str当中存储的内容是Unicode码点;
  • 为了敲代码方便,可以自己写一些帮助函数来做bytes与str之间的转换;
  • bytes和str是两种数据类型,不可以直接比较;
  • 如果想从文件中读写二进制文件,请在打开文件时指定二进制模式;
  • 如果想从文件中读写Unicode内容,需要注意系统的默认编码方式,最好明确指定具体的编码方式;

写本篇博客时,笔者依然去网上搜索了一下bytes与str的区别,有在Stack Overflow上找到一条很赞的回复,于是将其翻译于此处:

回答链接:

stackoverflow.com/questions/6…

翻译如下:

计算机能存储的内容,只能是bytes。

存储任何东西到计算机之前,我们都需要先对它进行编码以转化为bytes,例如:

  • 如果想存储音乐,首先要将其编码为MP3、WAV或者其它音频格式;
  • 如果想存储图片,得先将其编码为PNG、JPEG;
  • 如果想存储文本,需要先将其编码为ASCII或者UTF-8;

MP3、WAV、PNG、JPEG、ASCII和UTF-8,都是编码的示例。编码,是一种代表音频、图片、文字的字节(bytes)排列规则(格式)。

在Python当中,byte字符串仅仅是一种字节(bytes)序列,它并非人类可读。在此种情景下,任何东西想要存储到计算机之前,都需要将其编码为字节序列。

另一方面,经常被称作string的字符串,是人类可读的一种字符序列。这种字符串不能直接被计算机存储,它首先需要被编码为字节序列,有许多种——如ASCII、UTF-8、GBK——编码方式可以将字符序列变成字节序列。

'I am a string'.encode('ASCII')

上面这一行代码将字符串I am a string编码为ASCII格式的字节序列,如果将它打印出来,Python将会输出b'I am a string'。请注意,即便我们能看懂b'I am a string',它依然非人类可读,我们能看懂它的原因,只是因为print时Python将其从ASCII码解码为人类可读。Python当中,字节序列是由b开头的ASCII格式的一种序列。

当我们知道字节序列用什么格式编码时,它可以被此格式解码为字符串。

b'I am a string'.decode('ASCII')

上面这一行会返回原始字符串'I am a string'

总之:编码和解码是一对反向操作,不管什么内容,存储到硬盘之前都需要进行编码,而要将其变为人类可读,则必须先进行解码操作。

总之,bytes计算机可读,str人类可读,两者之间的转换通过encode、decode进行,转换时,需要指定具体的编码格式。

笔者阅读本书时,有同时查看CPython的源码,阅读本小节时,有去看一下编码解码相关内容,于源码中找到些之前不了解的内容,先记录于此处:

CPython源码中有一个目录叫做Lib,这算是Python的标准库,其中收录的内容有httprexml等常用模块,encodings也被包含于其中。在encodings模块中,是一种又一种类型的编码格式,比如UTF-8ASCIIGBK等等,查看编码解码的具体实现,可以从这些文件入手。

encodings模块是懒加载,只有真正用到某种编码格式时,才会加载相关模块。