105

检查memoryview 上的文档:

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

内存视图(obj)

创建一个引用 obj 的 memoryview。obj 必须支持缓冲区协议。支持缓冲区协议的内置对象包括字节和字节数组。

然后我们给出示例代码:

>>> 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:

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

人们建议解决方法,例如许多字符串和正则表达式函数采用可用于模拟推进指针的位置参数。这有两个问题:首先它是一种变通方法,你不得不改变你的编码风格来克服这些缺点,其次:不是所有的函数都有位置参数,例如正则表达式函数和startswithencode()/decode()不做。

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

编辑2:

代码示例会使事情更清楚。这就是我想要做的事情,而且我认为 memoryview 乍一看会让我做的事情。让我们使用 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
4

5 回答 5

98

s 有用的一个原因memoryview是它们可以在不复制基础数据的情况下进行切片,这与bytes/不同str

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

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

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

在我的电脑上,我得到

     bytes 100000 0.211
     bytes 200000 0.826
     bytes 300000 1.953
     bytes 400000 3.514
memoryview 100000 0.021
memoryview 200000 0.052
memoryview 300000 0.043
memoryview 400000 0.077

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

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

于 2015-12-13T22:51:16.680 回答
70

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

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

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

于 2013-09-06T10:31:18.673 回答
7

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

提问者和我一样,希望能够创建一个内存视图,它可以选择现有数组的一部分(例如字节或字节数组)。因此,我们期望类似:

desired_slice_view = memoryview(existing_array, start_index, end_index)

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

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

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

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

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

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

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

可以想象,memoryview 构造函数可以采用切片对象:

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

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

于 2019-02-11T10:46:50.197 回答
4

这是python3代码。

#!/usr/bin/env python3

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

for n in (100000, 200000, 300000, 400000):
    data = b'x'*n
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print ('memview {:d} {:f}'.format(n,time.time()-start))
于 2018-07-24T21:55:01.997 回答
2

锑的绝佳例子。实际上,在 Python3 中,您可以将 data = 'x'*n 替换为 data = bytes(n) 并将括号放在打印语句中,如下所示:

import time
for n in (100000, 200000, 300000, 400000):
    #data = 'x'*n
    data = bytes(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
    data = bytes(n)
    start = time.time()
    b = memoryview(data)
    while b:
        b = b[1:]
    print('memoryview', n, time.time()-start)
于 2019-02-28T07:57:51.443 回答