Python中的memoryview到底有什么意义

2024-06-09 08:58:54 发布

您现在位置:Python中文网/ 问答频道 /正文

检查内存视图中的documentation

memoryview objects allow Python code to access the internal data of an object that supports the buffer protocol without copying.

class memoryview(obj)

Create a memoryview that references obj. obj must support the buffer protocol. Built-in objects that support the buffer protocol include bytes and bytearray.

然后给出了示例代码:

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'

报价完毕,现在让我们仔细看一下:

>>> b = b'long bytes stream'
>>> b.startswith(b'long')
True
>>> v = memoryview(b)
>>> vsub = v[5:]
>>> vsub.startswith(b'bytes')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'memoryview' object has no attribute 'startswith'
>>> bytes(vsub).startswith(b'bytes')
True
>>> 

我从上面得到的信息:

我们创建一个memoryview对象来公开缓冲区对象的内部数据 但是,为了对对象执行任何有用的操作(通过调用方法 由对象提供),我们必须创建一个副本!

当我们有一个大对象时,通常需要memoryview(或旧的缓冲区对象), 切片也可以很大。对提高效率的需求将会出现 如果我们正在做大的切片,或者是做很多次的小切片。

有了上面的方案,我不明白它对任何一种情况都有什么用处,除非 有人可以向我解释我在这里错过了什么。

编辑1:

我们有大量的数据,我们希望通过从开始到 结束,例如从字符串缓冲区的开头提取标记,直到缓冲区被使用为止 任何需要缓冲区类型的函数。在python中如何做类似的事情?

人们建议解决方法,例如许多字符串和正则表达式函数 可用于模拟推进指针的参数。有两个问题:第一 这是一个变通的办法,你不得不改变你的编码风格来克服缺点,并且 第二:并非所有函数都有位置参数,例如regex函数和startswithdo,encode()/decode()

其他人可能会建议将数据分块加载,或者将缓冲区处理得很小 大于最大标记的段。好吧,我们知道这些可能 解决方法,但是我们应该以更自然的方式在python中工作 试图改变编码方式以适应语言的需要-不是吗?

编辑2:

代码示例可以使事情更清楚。这是我想做的,我以为记忆视图会让我第一眼看到。让我们使用pmview(正确的内存视图)来实现我正在寻找的功能:

tokens = []
xlarge_str = get_string()
xlarge_str_view =  pmview(xlarge_str)

while True:
    token =  get_token(xlarge_str_view)
    if token: 
        xlarge_str_view = xlarge_str_view.vslice(len(token)) 
        # vslice: view slice: default stop paramter at end of buffer
        tokens.append(token)
    else:   
        break

Tags: the对象函数tokenview视图bytesthat
3条回答

之所以memoryviews有用,是因为它们可以在不复制底层数据的情况下进行切片,而不像bytes/str

例如,以下面的玩具为例。

import time
for n in (100000, 200000, 300000, 400000):
    data = 'x'*n
    start = time.time()
    b = data
    while b:
        b = b[1:]
    print 'bytes', n, time.time()-start

for n in (100000, 200000, 300000, 400000):
    data = 'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print 'memoryview', n, time.time()-start

在我的电脑上,我得到

bytes 100000 0.200068950653
bytes 200000 0.938908100128
bytes 300000 2.30898690224
bytes 400000 4.27718806267
memoryview 100000 0.0100269317627
memoryview 200000 0.0208270549774
memoryview 300000 0.0303030014038
memoryview 400000 0.0403470993042

您可以清楚地看到重复字符串切片的二次复杂性。即使只有40万次迭代,也已经无法更改了。同时,memoryview版本具有线性复杂性,速度极快。

编辑:请注意,这是在CPython中完成的。There was a bug in Pypy up to 4.0.1 that caused memoryviews to have quadratic performance.

让我来解释一下理解上的缺陷在哪里。

发问者和我一样,希望能够创建一个memoryview,用于选择现有数组的一个片段(例如bytes或bytearray)。因此,我们期望:

desired_slice_view = memoryview(existing_array, start_index, end_index)

唉,没有这样的构造器,文档也没有说明该怎么做。

关键是,您必须首先创建一个覆盖整个现有数组的memoryview。从该memoryview可以创建第二个memoryview,覆盖现有数组的一部分,如下所示:

whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]

简而言之,第一行的目的只是提供一个对象,该对象的slice实现(dunder getitem)返回一个memoryview。

这可能看起来不整洁,但可以通过以下几种方式进行合理化:

  1. 我们想要的输出是一个内存视图,它是一个片段。通常,我们使用slice操作符[10:20]从同一类型的对象中获取切片对象。因此,我们有理由期望从memoryview中获得所需的“切片”视图,因此第一步是获得整个底层数组的memoryview。

  2. 带有start和end参数的memoryview构造函数的天真预期没有考虑到切片规范真正需要常规切片运算符的所有表示性(包括像[3::2]或[:-4]等)。没有办法只在一个线性构造函数中使用现有(且已理解)运算符。不能将它附加到现有的_数组参数,因为这将成为该数组的一个切片,而不是告诉memoryview构造函数一些切片参数。而且不能将运算符本身用作参数,因为它是运算符,而不是值或对象。

可以想象,memoryview构造函数可以获取slice对象:

desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )

。。。但这并不是很令人满意,因为用户必须了解slice对象及其构造函数的参数的含义,当他们已经考虑到slice操作符的表示法时。

当您需要只需要支持索引的二进制数据子集时,memoryview对象非常好。不必获取切片(并创建新的、可能很大的)对象来传递给另一个API,只需获取一个memoryview对象。

一个这样的API示例是struct模块。不是传入一个大的bytes对象片段来解析压缩的C值,而是传入一个memoryview区域,即需要从中提取值的区域。

memoryview对象,事实上,支持struct本机解包;您可以用一个切片针对底层bytes对象的一个区域,然后使用.cast()将底层字节“解释”为长整数、浮点值或n维整数列表。这使得二进制文件格式的解释非常有效,而不必创建更多的字节副本。

相关问题 更多 >