Python基础教程:深入理解bytes和bytearray数据类型的使用

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() 释放对象
  • 物联沃分享整理
    物联沃-IOTWORD物联网 » Python基础教程:深入理解bytes和bytearray数据类型的使用

    发表评论