bytes
与 bytearray
是python非常重要的数据类型,但其重要性经常被我们忽视了。在实际开发过程中,又总是遇到 bytes 类型。举例,pickle 序列化, json序列化就是将对象转为bytes类型。字符串编码问题也是1个常见的bytes相关问题,图像数据都是bytes类型,等等。
另外,bytes, bytearray 直接处理二进制数据, 处理速度比str, list, tuple等类型要快很多,适合性能要求高的应用开发,如图像处理,网络通信等。memoryview
提供了1种以数组方式访问内存数据的方式,进一步方便了bytes类型的使用。
本文将介绍bytes
类型数据的创建及各类操作,与其它类型之间的转换,字符串编码解码原理,bytearray
,memoryview
的基本语法以及常见使用方式。
可以多种方法生成bytes变量实例,如下面的例子
>>> bytes(b'hello world') # 使用字节串实例创建
b'hello world'
bytes(10) # 指定生成10个全零的字节串, 字节串空值就是0.
>>> bytes(10)
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
bytes(range(20)) # 生成1个序列
>>> bytes(range(20))
b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13'
bytes(binary_obj) # 如果是字符串对象,需要 encoding参数
字符串转bytes, 是用 encode() 方法
data = str_data.encode(encoding="utf-8")
>>> str = "你好,世界"
>>> byte_data = str.encode(encoding='utf-8')
>>> byte_data
b'\xe4\xbd\xa0\xe5\xa5\xbd\xef\xbc\x8c\xe4\xb8\x96\xe7\x95\x8c'
bytes 类型字符串在屏幕显示时,除了ascii字符外,其它字符无法以可读方式显示。
bytes 转字符串:
byte_data.decode(encoding="utf-8")
用int对象的to_bytes()方法
# int variable
num = 7
# int to bytes
num_bytes = num.to_bytes(2, byteorder='big')
# display result and type
print(num_bytes)
print(type(num_bytes))
第2个参数,表示双字节整数中高低字符顺序,big 表示正常顺序,即低字节在前,高字节在后。 本例输出:
b'\x00\x07'
little 表示,高字节在前,低字节在后。上例中,将2个参数改为little, 则显示出的数字为b’\x07\x00’
>>> num = 7
>>> num_bytes = num.to_bytes(2, byteorder='little')
>>> print(num_bytes)
b'\x07\x00'
>>> print(type(num_bytes))
bytes 转int , 使用 int.from_bytes()方法
>>> d=3324
>>> byte_data=d.to_bytes(5,"big")
>>> byte_data
b'\x00\x00\x00\x0c\xfc'
>>> int_data = int.from_bytes(byte_data,"big")
>>> int_data
3324
float类型无法直接转换为bytes, 可以先转为string, 再转为bytes.
dict, list, tuple 转bytes, 通常使用pickle序列化, 或用 json序列化
pickle 序列化就是将对象转为字节码,保存进文件,或者 bytes变量。
json也是字节串,只是各种软件都支持json格式识别,所以可以方便显示查看。
>>> import pickle
>>> dl
[30, 203, 3, 133333383]
>>> by10 = pickle.dumps(dl)
>>> type(by10)
<class 'bytes'>
>>> pickle.loads(by10)
[30, 203, 3, 133333383]
>>>
上面的例子可以看到,当用pick.dump(dl)序列化后,by10是1个bytes类型。
基本上字符串支持的操作,bytes 字节串也都支持,只不过是二进制方式,可读性较差。 .
如在字符串中查找、替换字符等
>>> a=b"alibaba"
>>> a.count(b'a')
3
>>> a.find(b'a',3)
4
>>> chr(a[4])
'a'
>>> b=a.replace(b'a',b'x')
>>> b
b'xlibxbx'
注意 bytes 类型是 immutable, 其值不可修改。
字符串的编码与解码,其实就是将指定协议,字符串转换为bytes类型,以及从bytes类型转回字符串的过程。
在计算机低层是用二进制码来运行的,只有0和1。计算机基本存储单位为字节, 8 比特(bit)等于1个字节(byte),即一个字节能表示的最大整数是 255(1111 1111)。最初的字符集格式为用1个字节来表示所有字符,也就是 ASCII 字符集,可以表示 256 个字符,支持英文字母,数字和少部分符号。
用1个字节用来表示1万个汉字显然不够,日文也存在同样问题。 为了统一编码方式来表示世界各国语言文字,出现了Unicode 字符集,它通常采用2个字节来表示1个字符。但有的字符多于2个字节。但对于法文等小字符集语言来说浪费了太多的比特位,因此在Unicode基础上又发展出可变长的UTF-8 编码方式,这也是python3默认的编码方式。
字符串是可读方式的数据。但字符串内容在内存中也是以二进制方式存储。可以认为,字符串在内存中的形式是指定编码格式的1块内存数据。例如 "Python字符“
这几个字符,5个英文字符 + 2个汉字,默认utf-8编码格式。Python UTF-8 编码,1个英文字母为1个字节,1个常用汉字是3个字节。
程序从字符串数据区读取数据的方式:
>>> len(bytes('Python字符','utf-8'))
12
>>>
由于 string 类还封装了许多其它方法与属性,以及与系统的交互,最终实现汉字在屏幕上显示出来,所以包含Python字符
的string对象在内存中的占据字节数远不止12个字节。但转换成bytes后只有12个字节,保存在文件中,在网络发送时,非常节省资源,
需要显式地对字符串进行编码的场景主要有:
data = strdata.encode( encoding="utf-8")
, 这时data 的类型是 bytesencod()
和 decode()
分别对应编码和解码函数,
编码:就是把可读的字符串,按指定编码格式,将字符逐一转换为二进制码,没有其它多余的数据。中文转换后,屏幕上显示为16进制数,只有ascii字符会显示,但在前面加个b’ ’ ,表示这是字节串。
my_b = '技能树'.encode('utf-8')
print('编码后',my_b) # 编码后 b'\xe6\x8a\x80\xe8\x83\xbd\xe6\xa0\x91'
>>> my_b = "hello world".encode('utf-8')
>>> my_b
b'hello world'
解码操作,就是把 bytes型数据转换为可读形式的数据类型,如下所示:
my_b = '技能树'.encode('utf-8')
print('编码后', my_b) # 编码后 b'\xe6\x8a\x80\xe8\x83\xbd\xe6\xa0\x91'
my_str = my_b.decode('utf-8')
print("解码后", my_str)
解码后出现乱码,通常是解码指定的编码格式与编码时的不一致。 而且这种问题通常发生在网络接口上。 最常见问题:
HTTP GET请求有中文参数时,常遇到编解码问题。
主要原因:http协议对URL参数的编码要求是ASCII字符集,汉字是UTF-8。在发送时要进行两次编码才能将汉字转为ASCII字节码:
接收方也要进行两次解码,才能正确地还原汉字。
还好python 内置库urllib 提供了1条命令,1次就可以将汉字转为ASCII编码。
编码: urllib.parse.urlencode(dic)
解码: urllib.parse.unquote(dic or str)
示例代码
keyword = "天气预报"
param_list = urllib.parse.urlencode( { 'q' : keyword } )
header = {'user-Agent':’haha‘}
url = 'http://www.baidu.com/s/'
response = request.get( url, params=param_list, headers = header )
字符编码值查看与转换
通过 ord()
函数获取字符的整数表示,通过 chr()
将整数转换为字符,例如下述代码
print(ord('爬')) # 29228
print(chr(29228))
bytearray 字节数组,使用方法与bytes类似,使用bytearray的主要原因:
创建bytearray变量语法:
variable_name = bytearray(source, encoding, errors),
每个元素为1个字符,如果不指定encoding, 默认是ascii,
>>> str = "Geeksforgeeks"
>>> array1 = bytearray(str, 'utf-8')
>>> print(array1)
bytearray(b'Geeksforgeeks')
>>> array1[3]
107
>>> chr(array1[3])
'k'
>>> len(array1)
13
>>> len(str)
13
>>>
中文字符转 bytearray
>>> str = "你好,世界"
>>> array2 = bytearray(str, 'utf-16')
>>> array2
bytearray(b'\xff\xfe`O}Y\x0c\xff\x16NLu')
>>>
int 转bytearray 类型
int型数据应放入1个数组,每个数据不大于255,否则报错
>>> dl = [ 30, 203, 3, 183]
>>> array3 = bytearray(dl)
>>> print(array3)
bytearray(b'\x1e\xcb\x03\xb7')
>>> len(array3)
4
>>> dl = [ 30, 203, 3, 133333383]
>>> array3 = bytearray(dl)
Traceback (most recent call last):
File "", line 1, in
ValueError: byte must be in range(0, 256)
在bytearray二进制字符串中查找字符
命令格式 bytearray.find(sub[, start[, end]])
>>> arr1 = bytearray(b"aaaahellocccc")
>>> arr1.find(b'h',0,)
4
>>>
string类型无法直接修改字符串,转为bytearray类型后可以进行修改。
>>> str1 = "Geeksforgeeks"
>>> array1 = bytearray(str1.encode())
>>> array1
bytearray(b'Geeksforgeeks')
>>> array1.replace(b's',b'x')
bytearray(b'Geekxforgeekx')
此示例,用ctypes 生成1个 c_ubyte类型数组,使用ctypes.memmove() 将该数组内容复制到barray变量,注意这是内存深拷贝方式。
data = (ctypes.c_ubyte *5)(0x11,0x22,0x33,0x44,0x55)
barray = bytearray(5)
ptr1 = (ctypes.c_ubyte * 5).from_buffer(barray)
ctypes.memmove(ptr1,data, 5)
print(f" barray[4] {barray[4]:#X}")
输出:
barray[4] 0X55
memoryview 对象允许 Python 代码访问一个对象的内部数据,只要该对象支持缓冲区协议而无需进行拷贝. 当前支持缓冲区协议的对象主要有bytes, bytearray, array.array数组。
可以理解为:memoryview 可用数组的方式来访问关联对象的数据,支持切片索引等数组的通用操作。从这点来看,memoryview 类似于C语言的指针功能。
示例,用 memoryview 来访问 bytes对象
>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>>v[-1]
103
>>>v[1:4]
>>>bytes(v[1:4])
b'bce
下层对象使用array的示例, 用数组方式访问成员,用tolist()方法可将数组转为list 类型
>>>import array
>>>a = array.array('l', [-11111111, 22222222, -33333333, 44444444])
>>>m = memoryview(a)
>>>m[0]
-11111111
>>>m[-1]
44444444
>>>m[::2].tolist()
[-11111111, -33333333]
如果下层对象是mutable, 也可以通过memory view来赋值
data = bytearray(b'abcefg')
v = memoryview(data)
v.readonly
False
v[0] = ord(b'z')
data
bytearray(b'zbcefg')
主要属性:
ndim
的长度给出以字节表示的大小,以便访问数组中每个维度上的每个元素主要方法
a = array.array('I', [1, 2, 3, 4, 5])
x = memoryview(a)
x.tolist()