您的位置:

深入解析Python中的memoryview

Python自带的memoryview可以说是一个高效的内存操作工具,可以让我们更好地处理二进制数据。在本文中,我们将从多个方面深入探讨memoryview,让我们一步一步去揭开它的神秘面纱。

一、memoryview是什么?

1、memoryview的概念

在Python中,memoryview是一个在不复制内容的情况下访问内存的工具。它是一个内存切片的概念,它允许我们在不复制数据的情况下访问和操作二进制数据。

2、memoryview的定义方法

# 定义方法
mv = memoryview(byte_array)

在上面的代码中,byte_array可以是bytes对象或bytearray对象。通过memoryview实例化一个对象后,它将提供一些方法来访问底层数据,例如mv[i]和mv[i:j](切片)。

3、memoryview的内存模型

创建一个memoryview对象时,Python存储器资源是以numpyndarray的方式进行管理的。它实际上是两个标准C结构的Python对象:

  • Py_buffer(也称为Py_buffer结构体),它定义了Numpy的ndarray存储器结构,并描述了访问这个存储器需要的所有信息。
  • MemoryViewObject,实质上是Python内部的一个主语义对象,它实现了一个memoryview对象实例。

二、memoryview在Python中有哪些用途?

1、memoryview优势

对于Python内置的数组数据类型(如NDArray和PyArrayObject)来说,memoryview的一个显著优点是它可以利用Python的GIL(全局解释器锁)被并行修改,而不会破坏数据的完整性和一致性。

2、memoryview的用途

  • Memoryview可以提高Python程序执行效率,因为它能够比较整块地存储和处理二进制数据。
  • Memoryview特别适用于块处理文件或网络数据流。
  • Memoryview对于多个并发任务处理二进制数据也特别有用,可以实现数据的并行访问而无需加锁。

三、常用的memoryview操作方法

1、memoryview选取

memoryview允许我们像操作普通的Python序列(例如list和tuple)一样进行切片操作。但是,与普通的Python序列不同的是,memoryview的切片并不会创建新的数据副本,而是直接返回底层数据的一个视图。

# 创建bytearray
bytearray_test = bytearray(b'abcd')

# 通过memoryview选取子对象
mv_test = memoryview(bytearray_test)
sub_mv_test = mv_test[1:3]
print(sub_mv_test)  # 

# 修改对象
sub_mv_test[:] = b'xy'

print(mv_test.tobytes())  # b'axyd'

  

2、memoryview.cast

通过memoryview对象可以提供对同一个内存区域的不同切片视图,还可以使用memoryview.cast方法创建一个对象的内存视图。

import numpy as np

# 创建一个int8数组
array_test = np.zeros((3,), dtype=np.int8)
array_test_pointer = memoryview(array_test)

# 将int8数组地址转换成int32数组地址,并创建memoryview
int32_array_pointer = array_test_pointer.cast('i')
print(int32_array_pointer.format)  # 'i'
print(int32_array_pointer.itemsize)  # 4
print(int32_array_pointer.shape)  # (3,)

3、memoryview.bytes_per_element

bytes_per_element是memoryview对象的一个属性,返回单个元素的大小(以字节为单位)。

array_bytes = bytearray(b'123456')
array_pointer = memoryview(array_bytes)

print(array_pointer.bytes_per_element)  # 1

四、memoryview与USB设备交互的示例

下面将演示如何利用memoryview和PyUSB连接USB设备,读取USB设备返回的二进制数据并进行处理。

import usb.core
import usb.util

# 查找USB设备
device = usb.core.find(idVendor=0x1234, idProduct=0x5678)

# 设置配置并进行USB设备输出流的连接
if device is not None:
    endpoint_out = device[0][(0, 0)][0]
    device.write(endpoint_out, 'test')

    # 读取USB设备返回的二进制数据
    endpoint_in = device[0][(0, 0)][1]
    device.read(endpoint_in.bEndpointAddress, endpoint_in.wMaxPacketSize)

    # 利用memoryview处理返回的二进制数据
    response_mv = memoryview(device.read(endpoint_in.bEndpointAddress, endpoint_in.wMaxPacketSize))

五、memoryview的局限性

1、不能被序列化

memoryview对象不能被Python pickle序列化,因为它们是包含底层内存地址和长度信息的指针。这意味着如果需要序列化,必须在调用pickle模块之前先转换为bytes或bytearray类型。

2、不能跨进程使用

由于memoryview基于底层内存视图的概念,因此它们通常只对包含它们的那个Python程序实例可用,而且不能被另一个Python程序实例共享。这也是因为操作系统的Kernal通常不允许一个进程访问另一个进程的内存。

3、不适用于所有二进制数据类型

虽然memoryview可以处理多种类型的二进制数据(例如bytes对象和numpy数组),但是对于C结构之类的复杂类型,memoryview可能不是一个良好的选择。这是因为指针操作和内存布局可能存在非常细微的差异,这将使得memoryview无法精确地处理它。

六、结语

通过本文对memoryview的深入剖析,我们了解了它的概念、优势、用途以及实际应用示例,同时也揭示了它的局限性。对于经常需要处理二进制数据的Python开发人员来说,memoryview无疑是一个非常有用的工具。