Python中的memoryview究竟是什么意思

时间:2013-09-06 10:28:37

标签: python buffer memoryview

检查内存视图上的documentation

  

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'

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

>>> 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(或旧的缓冲区对象), 切片也可以很大。需要提高效率 如果我们正在做大片,或者做很多次小片。

根据上述方案,我不知道它对两种情况都有用,除非 有人可以向我解释我在这里缺少的东西。

EDIT1:

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

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

其他人可能建议以块的形式加载数据,或者以小的方式处理缓冲区 大于最大令牌的段。好的,所以我们知道这些可能 解决方法,但我们应该在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

4 个答案:

答案 0 :(得分:54)

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.

答案 1 :(得分:2)

这是python3代码。

#!/usr/bin/env python3

import time
for n in (100000, 200000, 300000, 400000):
    data = '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))

答案 2 :(得分:0)

让我说清楚理解这里的故障所在。

发问者和我一样,希望能够创建一个内存视图,该视图选择现有数组的一个切片(例如字节或字节数组)。因此,我们期望像这样:

desired_slice_view = memoryview(existing_array, start_index, end_index)

A,没有这样的构造函数,文档也没有说明该怎么做。

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

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

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

这似乎很麻烦,但是可以通过以下两种方法对其进行合理化:

  1. 我们所需的输出是一个内存视图,该内存视图是某些内容的一部分。通常,我们通过使用切片运算符[10:20]从相同类型的对象中获得切片的对象。因此,出于某些原因,我们需要从内存视图中获取desired_slice_view,因此第一步就是获取整个基础数组的内存视图。

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

可以想象,一个memoryview构造函数可以采用一个slice对象:

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

...但是,这并不是很令人满意,因为当用户已经考虑了slice运算符的含义时,他们将不得不了解slice对象及其构造函数的参数含义。

答案 3 :(得分:0)

锑业的典范。 实际上,在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)