Python中的memoryview究竟是什么意思

Bas*_*ani 68 python buffer memoryview

检查内存视图上的文档:

memoryview对象允许Python代码访问支持缓冲区协议的对象的内部数据而无需复制.

class memoryview(obj)

创建一个引用obj的内存视图.obj必须支持缓冲协议.支持缓冲区协议的内置对象包括bytes和bytearray.

然后我们给出示例代码:

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

报价结束,现在让我们仔细看看:

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

所以我从上面收集的内容:

我们创建一个memoryview对象来公开缓冲区对象的内部数据而不进行复制,但是,为了对对象做任何有用的事情(通过调用对象提供的方法),我们必须创建一个副本!

当我们有一个大对象时,通常需要memoryview(或旧的缓冲区对象),并且切片也可能很大.如果我们正在制作大切片,或者制作小切片但需要很多次,则需要提高效率.

有了上述方案,我看不出它对两种情况都有用,除非有人能向我解释我在这里缺少的东西.

EDIT1:

我们有大量数据,我们希望通过从头到尾推进它来处理它,例如从字符串缓冲区的开头提取标记,直到缓冲区被消耗.在C语言中,这是推进指针通过缓冲区,指针可以传递给任何期望缓冲区类型的函数.如何在python中完成类似的事情?

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

其他人可能会建议以块的形式加载数据,或者以大于最大令牌的小段处理缓冲区.好的,所以我们知道这些可能的解决方法,但我们应该在python中以更自然的方式工作,而不是试图改变编码风格以适应语言 - 不是吗?

EDIT2:

代码示例可以使事情更清晰.这就是我想做的事情,而我认为记忆视图可以让我乍看之下.让我们使用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
Run Code Online (Sandbox Code Playgroud)

Ant*_*ony 67

一个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
Run Code Online (Sandbox Code Playgroud)

在我的电脑上,我明白了

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
Run Code Online (Sandbox Code Playgroud)

您可以清楚地看到重复字符串切片的二次复杂性.即使只有400000次迭代,它已经无法控制了.同时,memoryview版本具有线性复杂性并且闪电般快速.

编辑:请注意,这是在CPython中完成的.Pypy中有一个高达4.0.1的错误导致内存视图具有二次性能.

  • 这个答案没有解决这样一个事实,即做任何“有用”的事情,因为提问者说你必须使用 bytes() 来复制对象...... (8认同)
  • @citizen2077 正如我的示例所示,即使您最终将其复制到字节对象,它对于有效地执行中间操作也很有用。 (5认同)

Mar*_*ers 44

memoryview当您需要仅需要支持索引的二进制数据子集时,对象很棒.而不是必须采取切片(并创建新的,可能很大的)对象来传递给另一个API,您只需要一个memoryview对象.

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

memoryview事实上,对象struct本身支持解包; 您可以bytes使用切片来定位基础对象的区域,然后使用.cast()"解释"基础字节作为长整数,浮点值或整数的n维列表.这使得非常有效的二进制文件格式解释成为可能,而不必创建更多的字节副本.

  • @BaselShishani:切片内存视图会返回一个覆盖该区域的新内存视图. (5认同)
  • @BaselShishani:不使用`memoryview`。您正在处理文本,而不是二进制数据。 (2认同)

gwi*_*man 5

让我弄清楚这里的理解错误在哪里。

提问者和我一样,希望能够创建一个内存视图来选择现有数组的一部分(例如字节或字节数组)。因此,我们预计会出现以下情况:

desired_slice_view = memoryview(existing_array, start_index, end_index)
Run Code Online (Sandbox Code Playgroud)

唉,没有这样的构造函数,文档也没有说明要做什么。

关键是你必须首先制作一个覆盖整个现有数组的内存视图。从该内存视图中,您可以创建第二个内存视图来覆盖现有数组的一部分,如下所示:

whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]
Run Code Online (Sandbox Code Playgroud)

简而言之,第一行的目的只是提供一个对象,其切片实现(dunder-getitem)返回一个内存视图。

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

  1. 我们想要的输出是一个内存视图,它是某物的一部分。通常,我们通过在其上使用切片运算符 [10:20] 从相同类型的对象中获取切片对象。所以有一些理由期望我们需要从 memoryview 中获取我们的 desired_slice_view,因此第一步是获取整个底层数组的 memoryview。

  2. 对带有 start 和 end 参数的 memoryview 构造函数的幼稚期望未能考虑到切片规范确实需要通常切片运算符的所有表达能力(包括 [3::2] 或 [:-4] 等)。无法在该单行构造函数中仅使用现有(并理解)的运算符。您不能将它附加到 existing_array 参数,因为这将创建该数组的一个切片,而不是告诉 memoryview 构造函数一些切片参数。并且您不能将运算符本身用作参数,因为它是运算符而不是值或对象。

可以想象,一个内存视图构造函数可以接受一个切片对象:

desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )
Run Code Online (Sandbox Code Playgroud)

...但这并不是很令人满意,因为当用户已经根据切片运算符的符号进行思考时,他们必须了解切片对象及其构造函数的参数含义。