对Python中bytes的学习心得
前言
本人使用Python已有5年时间,但由于从事的是深度学习行业,因此并没有对Python有过较深的研究和掌握。前些时间打算温故知新,重新阅读Python编程教程,无意中看到了Python中有bytes类型和相关方法,因此做了一些充电,在此记录一下自己的心得。
1、什么是bytes
Python中的bytes类型用来表示字符的字节编码,即将原始字符转换为不同类型的编码序列,转换后的编码以字节为单位存储。在C++等语言中字符和字符串是两种不同的类型,字符类型保存了单个字符,例如一个字母、数字、标点或特殊符号,它们一般占用内存的一个字节,也可以保存单个中文汉字或中文标点,一般占用两个字节容量。
ascii字符类型在C++等语言中可以直接参与算术运算,运算时每一个字符按它们各自的ascii码值作为数字表示。依然记得当时记了一些字符的ascii码,例如‘a’是97,‘A’是65等。
而在Python中没有字符这种类型,单个字符也会被当作字符串处理,因此为方便编码转换而有了bytes类型。bytes可以方便地将不同格式的文件读入而不至于产生乱码,如文本、图片、视频等,也能够方便数据在电脑之间或网上传输而避免乱码和缺失等问题。
2、bytes的用法—编码和解码
Python中提供了三种方式来将字符串转换为bytes类型:
1、对于字母、数字、英文标点这样的ascii字符,直接在其前面缀上‘b’即可,如下所示:
s = b'I am learning Python programming.'
print(s)
#结果输出:b'I am learning Python programming.'
输出结果表明,字符串s已经被转换为bytes类型了。
2、对于含有汉字等其它字符的字符串,可以使用bytes构造方法来实现转换,代码如下:
s = bytes('我正在学习Python编程', encoding='utf-8')
print(s)
#结果输出:b'\xe6\x88\x91\xe6\xad\xa3\xe5\x9c\xa8\xe5\xad\xa6\xe4\xb9\xa0Python\xe7\xbc\x96\xe7\xa8\x8b'
结果表明字符串也实现了bytes转换。方法中的‘encoding’参数用来指定编码类型,如‘utf-8’、’gbk‘、’gb2312‘等可以选择,不指定类型则默认为’utf-8’。对非ascii字符而言,不同的编码类型所得到的bytes值也不同,同时bytes编码也占用两个字节。
3、对于2中非ascii字符的情况,也可以使用encode方法实现bytes转换,代码如下:
s = '我正在学习Python编程'
print(s.encode('utf-8'))
#结果输出:b'\xe6\x88\x91\xe6\xad\xa3\xe5\x9c\xa8\xe5\xad\xa6\xe4\xb9\xa0Python\xe7\xbc\x96\xe7\xa8\x8b'
同时,Python也提供了将bytes类型字符串还原为原始字符串的decode方法,其参数为编码类型,使用方法如下:
s = b'Python'
print(s.decode('utf-8'))
#结果输出:Python
默认解码类型也是‘utf-8’,要注意的是解码类型要与s编码时的bytes类型一致,否则会出现转换错误,例如下列情况:
s = '我正在学习Python编程'.encode('utf-8')
print(s.decode('gbk'))
#结果输出:Traceback (most recent call last):
File "/Users/penny/PycharmProjects/Programming/test.py", line 10, in <module>
print(s.decode('gbk'))
UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 4: illegal multibyte sequence
解释器抛出UnicodeDecodeError异常,原因是字符串编码和解码所采用的规范不一致,导致了在解析非ascii字符时出现错误。
值得一提的是,在ascii字符集合中,不同编码规范下的bytes值是相同的,因此如果字符串中只包含这些字符,那么就算编码和解码不一致程序也不报错,如下测试所示:
s = 'Python'.encode('utf-8')
print(s.decode('gbk'))
#输出结果:Python
3、bytes的用法—字符的数值转换
既然bytes能够将字符串转换为编码序列,那么我们可以像C++那样将字符转换为数值类型,以满足编程中的需要。前面已经介绍过,C++中单个字符类型可以直接表示成对应的ascii数值,以便参与运算,而Python中是没有字符类型的,因此若要获取字符的编码值则需要借助下标索引。例如对字符串’ Python’,想要获取‘P’和’n’的编码值则需要使用以下方式:
s = b'Python'
print(s[0], s[5])
#输出结果:80 110
其中0和5对应着字符串中’P’和’n’的索引。对于汉字等非ascii字符,也需要先转换为bytes,再通过索引的方式获取编码值,只是汉字的编码值在一般程序设计中并不常用,值得注意的是由于汉字等字符的bytes为2个字节,因此编码中也由两个数值构成。
4、bytes的用法—文件的二进制读写
文件的读写操作分为普通和二进制两种,大多数情况下普通读写即可满足日常需求。二进制读写一般用于读取非文本文件时,例如图片、视频等。
我们探讨以二进制方式打开文本文件的情况,假设一个文本文件中保存了字符串’我正在学习Python编程’,以二进制将其打开并读入后,得到的就是该字符串的bytes类型,如下所示:
with open('a.txt', 'rb') as f:
print(f.read())
#文件‘a.txt’中的内容:我正在学习Python编程
#输出结果:b'\xe6\x88\x91\xe6\xad\xa3\xe5\x9c\xa8\xe5\xad\xa6\xe4\xb9\xa0Python\xe7\xbc\x96\xe7\xa8\x8b'
而以二进制写入文本文件时,需要以bytes类型的字符串作为输入,否则程序就会报错,如下所示:
with open('a.txt', 'wb') as f:
print(f.write('我正在学习Python编程'))
#输出结果:Traceback (most recent call last):
File "/Users/penny/PycharmProjects/Programming/test.py", line 6, in <module>
print(f.write('我正在学习Python编程'))
TypeError: a bytes-like object is required, not 'str'
错误提示为TypeError,要求输入必须为bytes相关类型。
我们将上文中的‘我正在学习Python编程’对应的bytes类型作为write函数的输入,以观察a.txt中的内容变化,结果如下所示:
with open('a.txt', 'wb') as f:
print(f.write(b'\xe6\x88\x91\xe6\xad\xa3\xe5\x9c\xa8\xe5\xad\xa6\xe4\xb9\xa0Python\xe7\xbc\x96\xe7\xa8\x8b'))
#程序执行前,a.txt内容为空。程序执行后,内容变为‘我正在学习Python编程’
由上述文件IO的测试结果表明,Python中的bytes类型也是用于二进制文件的输入和输出的。这里要注意的一点是bytes的编码必须和文件打开时指定的编码一致,否则也会报错。
4、字节数组—bytearray()
Python中的bytes类型是不可变的,这就意味着一旦将Python的字符串转换为bytes,就无法修改字符了,这点也较为不方便,因此Python提供了可变版本的bytes转换,方法名为bytesarray(),它可以更改转换后的bytes值。我们先看一个例子:
s = bytearray('Python', encoding='utf-8')
s[0]+=32
s = s.decode(encoding='utf-8')
print(s)
#输出结果:python
由结果不难看出,在对可变字符序列中的第一个bytes值增加偏移量32后,字符串中的‘P’变成了小写字母“p”. 接下来我们尝试实现两个功能—字符串的加密,以及输出UTF-8中的前20个汉字。
4.1 字符串的加密
很多应用场景下需要对传输信息进行加密,以保证安全性,我们使用bytearray来实现一个简单的字符串加密和解密功能,要求加密后的字母是原内容在字母表后的第10个值,即偏移量为10,如果超出字母表则从A和a开始继续计数。代码如下:
def strencode(string):
string_bytes = bytearray(string, encoding='utf-8')
for i in range(len(string_bytes)):
if 65<=string_bytes[i]<91 or 97<=string_bytes[i]<123:
string_bytes[i] = 65+(string_bytes[i]-65+10)%26 if 65<=string_bytes[i]<91 else 97+(string_bytes[i]-97+10)%26
return string_bytes.decode(encoding='utf-8')
def strdecode(string):
string_bytes = bytearray(string, encoding='utf-8')
for i in range(len(string_bytes)):
if 65<=string_bytes[i]<91 or 97<=string_bytes[i]<123:
string_bytes[i] = 90-((90-string_bytes[i])+10)%26 if 65<=string_bytes[i]<91 else 122-((122-string_bytes[i])+10)%26
return string_bytes.decode(encoding='utf-8')
print(strencode('abcdef')) #输出结果:klmnop
print(strdecode('KLMPOP')) #输出结果:ABCFEF
代码中我们分别实现了加密函数strencode()和解密函数strdecode(),通过对不同的用例测试,结果表明bytearray的字符偏移计算能够有效地通过偏移编码值来改变字符。
4.2 输出Unicode编码表中的前20个汉字
在工作中有时需要查看编码表中的字符分布,以有效地定位或筛选不同的字符,本例我们筛选出utf-8编码表中位于前20的汉字,代码实现如下:
first20 = []
word = bytearray('一', encoding='utf-8')
for i in range(20):
first20.append(word.decode())
word[-1] += 1
print(first20)
#输出结果:['一', '丁', '丂', '七', '丄', '丅', '丆', '万', '丈', '三', '上', '下', '丌', '不', '与', '丏', '丐', '丑', '丒', '专']
值得注意的是,汉字在utf-8中由3个256位的bytes编码组成,因此迭代时需要将最后一个bytes加1才能定位到下一个汉字,如果改变了前两个编码则输出的就不是连续的汉字,甚至可能报错。上述测试结果表明,我们的代码能够实现utf-8中前20个汉字的打印。
好了,对于bytes的学习心得就分享到这了,如果小伙伴们阅读时发现了错误或不足之处,可以随时向博主提出哦,博主先闪了!