Pythonのmemoryviewのポイントは正確には何ですか


86

memoryviewのドキュメントを確認する:

memoryviewオブジェクトを使用すると、Pythonコードは、コピーせずにバッファプロトコルをサポートするオブジェクトの内部データにアクセスできます。

クラスmemoryview(obj)

objを参照するmemoryviewを作成します。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(または古いバッファオブジェクト)が必要になり、スライスも大きくなる可能性があります。大きなスライスを作成する場合、または小さなスライスを作成するが何度も作成する場合は、効率を高める必要があります。

上記のスキームでは、誰かが私がここで欠けているものを私に説明できない限り、それがどちらの状況にもどのように役立つかわかりません。

編集1:

データのチャンクが大きいので、最初から最後までデータを進めて処理したいと考えています。たとえば、文字列バッファの先頭からバッファが消費されるまでトークンを抽出します。C用語では、これはポインタをバッファ、およびポインタはバッファタイプを期待する任意の関数に渡すことができます。Pythonで同様のことをどのように行うことができますか?

人々は回避策を提案します。たとえば、多くの文字列関数と正規表現関数は、ポインタの前進をエミュレートするために使用できる位置引数を取ります。これには2つの問題があります。1つは回避策であり、欠点を克服するためにコーディングスタイルを変更する必要があります。2つ目は、すべての関数に位置引数があるわけではありません。たとえば、正規表現関数とstartswith実行encode()/decode()しないです。

他の人は、データをチャンクでロードするか、最大トークンよりも大きい小さなセグメントでバッファーを処理することを提案するかもしれません。さて、私たちはこれらの可能な回避策を知っていますが、言語に合うようにコーディングスタイルを曲げようとせずに、Pythonでより自然な方法で作業することになっています-そうではありませんか?

Edit2:

コードサンプルは物事をより明確にするでしょう。これが私がやりたいことであり、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


9
参照された質問の回答は詳細を提供していません。また、質問は学習者の視点から潜在的な問題に触れていません。
バーゼルシシャニ2014

回答:


83

一つの理由 memoryviewが役立つのは、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

繰り返される文字列スライスの2次の複雑さをはっきりと見ることができます。たった400000回の反復でも、すでに管理できません。一方、memoryviewバージョンは線形の複雑さを持ち、非常に高速です。

編集:これはCPythonで行われたことに注意してください。4.0.1までのPypyにはバグがあり、memoryviewsのパフォーマンスが2次になりました。


この例はPython3では機能しませんTypeError: memoryview: a bytes-like object is required, not 'str'
微積分

ステートメントprintとしての@JoseもPython3では機能しません。このコードはPython2用に作成されていますが、Python3に必要な変更はごくわずかです。
アンチモン

@Yumi Tada str、python3では、python2で定義されているものとはまったく異なります。
hcnhcn012 2017

5
この答えは、あなたがバイトを(使用する必要がアスカー状態として「役に立つ」何かをするという事実に対処していない)がコピー対象...
ragardner

1
@ citizen2077私の例が示すように、最終的にバイトオブジェクトにコピーした場合でも、中間操作を効率的に行うのに役立ちます。
アンチモン

59

memoryviewオブジェクトは、インデックス作成をサポートするだけでよいバイナリデータのサブセットが必要な場合に最適です。別のAPIに渡すためにスライスを取得する(そして新しい、潜在的に大きなオブジェクトを作成する)代わりに、memoryviewオブジェクトを取得することができます。

そのようなAPIの例の1つは、structモジュールです。bytesパックされたC値を解析するために大きなオブジェクトのスライスを渡す代わりに、memoryview値を抽出する必要がある領域だけを渡します。

memoryview実際、オブジェクトはstructネイティブでの解凍をサポートしています。基になるbytesオブジェクトの領域をスライスでターゲットにして.cast()から、基になるバイトを長整数、浮動小数点値、または整数のn次元リストとして「解釈」するために使用できます。これにより、バイトのコピーをさらに作成することなく、非常に効率的なバイナリファイル形式の解釈が可能になります。


1
そして、インデックス作成以上のものをサポートするサブセットが必要な場合はどうしますか?!
バーゼルシシャニ2013

2
@BaselShishani:を使用しないでくださいmemoryview。その場合、バイナリデータではなく、テキストを処理します。
マルタインピータース

はい、テキストを扱います。したがって、memoryviewは使用しませんが、代替手段はありますか?
バーゼルシシャニ2013

どのような問題を解決しようとしていますか?テストする必要のある部分文字列はそれほど大きくありますか?
MartijnPieters

6
@BaselShishani:memoryviewをスライスすると、その領域だけをカバーする新しいmemoryviewが返されます。
マルタインピータース

5

ここで理解のグリッチがどこにあるのかをはっきりさせておきましょう。

質問者は、私のように、既存の配列(たとえば、バイトまたはバイト配列)のスライスを選択するメモリビューを作成できることを期待していました。したがって、次のようなものを期待していました。

desired_slice_view = memoryview(existing_array, start_index, end_index)

残念ながら、そのようなコンストラクターはなく、ドキュメントは代わりに何をすべきかを示していません。

重要なのは、最初に既存のアレイ全体をカバーするメモリビューを作成する必要があるということです。そのmemoryviewから、次のように、既存の配列のスライスをカバーする2番目のmemoryviewを作成できます。

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

つまり、最初の行の目的は、スライスの実装(dunder-getitem)がmemoryviewを返すオブジェクトを提供することです。

それは乱雑に見えるかもしれませんが、いくつかの方法でそれを合理化することができます:

  1. 必要な出力は、何かのスライスであるメモリビューです。通常、スライス演算子[10:20]を使用して、同じタイプのオブジェクトからスライスされたオブジェクトを取得します。したがって、memoryviewからdesired_slice_viewを取得する必要があると予想する理由がいくつかあります。したがって、最初のステップは、基になる配列全体のmemoryviewを取得することです。

  2. start引数とend引数を持つmemoryviewコンストラクターの素朴な期待は、スライス仕様が通常のスライス演算子([3 :: 2]や[:-4]などを含む)のすべての表現力を本当に必要とすることを考慮していません。そのワンライナーコンストラクターで既存の(そして理解されている)演算子を使用する方法はありません。これをexisting_array引数にアタッチすることはできません。これは、memoryviewコンストラクターにいくつかのスライスパラメーターを指示する代わりに、その配列のスライスを作成するためです。また、演算子自体は演算子であり、値やオブジェクトではないため、引数として使用することはできません。

おそらく、memoryviewコンストラクターはスライスオブジェクトを取ることができます。

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

...しかし、スライス演算子の表記法についてすでに考えている場合、ユーザーはスライスオブジェクトとそのコンストラクターのパラメーターの意味について学習する必要があるため、これはあまり満足のいくものではありません。


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

1

アンチモンによる優れた例。実際、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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.