bytesbytearray 是python非常重要的数据类型,但其重要性经常被我们忽视了。在实际开发过程中,又总是遇到 bytes 类型。举例,pickle 序列化, json序列化就是将对象转为bytes类型。字符串编码问题也是1个常见的bytes相关问题,图像数据都是bytes类型,等等。
另外,bytes, bytearray 直接处理二进制数据, 处理速度比str, list, tuple等类型要快很多,适合性能要求高的应用开发,如图像处理,网络通信等。memoryview提供了1种以数组方式访问内存数据的方式,进一步方便了bytes类型的使用。

本文将介绍bytes类型数据的创建及各类操作,与其它类型之间的转换,字符串编码解码原理,bytearraymemoryview 的基本语法以及常见使用方式。

1、生成bytes变量的方法:

可以多种方法生成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与各种数据类型之间的转换

字符串转bytes

字符串转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 转 bytes

用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'
<class 'bytes'>

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))
<class '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, tuple, list 转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 变量内容的操作

基本上字符串支持的操作,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, 其值不可修改。

2、字符串编码与解码

字符串的编码与解码,其实就是将指定协议,字符串转换为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个字节。
程序从字符串数据区读取数据的方式:

  • 第1-6字节,单字节转换为英文显示
  • 第7到12个字节,每3个字节为1个汉字编码,
>>> len(bytes('Python字符','utf-8'))
12
>>>

由于 string 类还封装了许多其它方法与属性,以及与系统的交互,最终实现汉字在屏幕上显示出来,所以包含Python字符 的string对象在内存中的占据字节数远不止12个字节。但转换成bytes后只有12个字节,保存在文件中,在网络发送时,非常节省资源,

需要显式地对字符串进行编码的场景主要有

  • 网络通信的发送侧, socket传输数据需要按字节传送,以占用更少网络资源, 因此通常需要将字符串提前转为字节码,也就是告知socket,,这块数据是用utf-8编码的,方式就是 data = strdata.encode( encoding="utf-8"), 这时data 的类型是 bytes
  • 在网络接收侧,收到数据,或者从文件中得到1块数据,且知道编码格式,用decode() 方法将字节数据转为字符串。
  • 用序列化方式保存文件,先将字符串转换为二进制编码,必须要指定编码格式,转换为二进制后保存,可以节省一些空间,同时提高了保密性。
  • 使用 ctyps 进行 c/c++函数参数转换时,c++字符串,指针等类型,对应的都是python的bytes类型。
  • Internet 邮件,HTTP URL请求参数只允许ASCII字符,需要将特殊字符,汉字等转码为ASCII. ( 但这种方式只是转码,不存在类型转变)

Base64 实际与 URL转码 性质很相似,也不会改变数据类型。 输入为byters,输出仍是。输入为str, 输出也是str. 因此不再赘述。

Python 编码和解码函数

encod()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字节码:

  • 第1次编码, 用 UTF-8 字符集,每个字符占3个字节。
  • 第2次编码,可以用 iso-8859-1,也可以用其它字符集,转为ASCII。

接收方也要进行两次解码,才能正确地还原汉字。

还好python 内置库urllib 提供了1条命令,1次就可以将汉字转为ASCII编码。
编码: urllib.parse.urlencode(dict ) 或者 urllib.parse.quote(string,encoding=“utf-8”)
解码: urllib.parse.unquote(encoded_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))

3、Bytearray的使用

3.1 bytearray 类型的主要使用场景

bytearray 字节数组,使用方法与bytes类似,使用bytearray的主要原因:

  • 对bytes类型的数据进行增删改场景,如需要修改二进制文件,图片,音频文件等。
  • bytearray是 mutable,即可以修改元素值。也支持切片索引。sring类型是immutable,所以bytearray适用于对字符串进行增删改时。
  • 调用 C/C++ 函数时,用于获取二进制数据类型指针变量内容

3.2 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 "<stdin>", line 1, in <module>
ValueError: byte must be in range(0, 256) 

3.3 对字符串进行操作

在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')

3.4 获取ctypes 指针变量内容

此示例,用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

4. 内存视图 Memoryview

memoryview 对象允许 Python 代码访问一个对象的内部数据,只要该对象支持缓冲区协议而无需进行拷贝. 当前支持缓冲区协议的对象主要有bytes, bytearray, array.array数组。
可以理解为:memoryview 可用数组的方式来访问关联对象的数据,支持切片索引等数组的通用操作。从这点来看,memoryview 类似于C语言的指针功能。

示例,用 memoryview 来访问 bytes对象

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>>v[-1]
103
>>>v[1:4]
<memory at 0x7f3ddc9f4350>
>>>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')

主要属性:

  • obj 内存视图的下层对象
  • readonly true/false
  • itemsize 每个元素的大小, 对于bytes, bytearray,此值为1
  • ndim 表示内存所代表的多维数组具有多少个维度
  • shape 表示 memoryview数组的维度
  • strides 一个整数元组,通过 ndim 的长度给出以字节表示的大小,以便访问数组中每个维度上的每个元素

主要方法

  • tolist(), 导出为list
a = array.array('I', [1, 2, 3, 4, 5])
x = memoryview(a)
x.tolist()
  • tobytes() 将缓冲区中的数据作为字节串返回。 这相当于在内存视图上调用 bytes 构造器。
  • hex() 用16进制表示缓冲区中的字节
  • release() 释放对象
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐