Python에서 memoryview의 요점은 정확히 무엇입니까


84

memoryview에 대한 문서 확인 :

memoryview 객체를 사용하면 Python 코드가 복사없이 버퍼 프로토콜을 지원하는 객체의 내부 데이터에 액세스 할 수 있습니다.

클래스 memoryview (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 객체를 생성합니다. 그러나 객체에 대해 유용한 작업을 수행하려면 (객체가 제공하는 메소드를 호출하여) 사본을 생성해야합니다!

일반적으로 메모리 뷰 (또는 이전 버퍼 객체)는 큰 객체가있을 때 필요하며 슬라이스도 클 수 있습니다. 큰 조각을 만들거나 작은 조각을 여러 번 만들면 더 나은 효율성이 필요합니다.

위의 계획을 사용하면 누군가 내가 여기서 놓친 것을 설명 할 수 없다면 어떤 상황에서도 유용 할 수 있는지 알 수 없습니다.

편집 1 :

우리는 많은 양의 데이터를 가지고 있으며, 예를 들어 버퍼가 소모 될 때까지 문자열 버퍼의 시작 부분에서 토큰을 추출하는 것과 같이 처음부터 끝까지 진행하여 처리하려고합니다 .C 용어에서 이것은 포인터를 통해 포인터를 이동하는 것입니다. 버퍼 및 포인터는 버퍼 유형을 예상하는 모든 함수에 전달할 수 있습니다. 파이썬에서 비슷한 일을 어떻게 할 수 있습니까?

사람들은 해결 방법을 제안합니다. 예를 들어 많은 문자열 및 정규식 함수가 포인터 전진을 에뮬레이트하는 데 사용할 수있는 위치 인수를 사용합니다. 여기에는 두 가지 문제가 있습니다. 첫 번째는 해결 방법이고, 단점을 극복하기 위해 코딩 스타일을 변경해야합니다. 두 번째 : 모든 함수에 위치 인수가있는 것은 아닙니다 (예 : regex 함수 및 startswith수행 encode()/ decode()하지 않음).

다른 사람들은 데이터를 청크로로드하거나 최대 토큰보다 큰 작은 세그먼트로 버퍼를 처리하도록 제안 할 수 있습니다. 좋아, 우리는 이러한 가능한 해결 방법을 알고 있지만, 코딩 스타일을 언어에 맞게 구부리지 않고 파이썬에서 더 자연스러운 방식으로 작업해야합니다. 그렇지 않습니까?

편집 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


9
참조 된 질문의 답변은 세부 정보를 제공하지 않습니다. 질문은 학습자의 관점에서 잠재적 인 문제를 다루지도 않습니다.
Basel Shishani 2014

답변:


81

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

반복되는 스트링 슬라이싱의 2 차 복잡성을 명확하게 볼 수 있습니다. 400000 회만 반복해도 이미 관리가 불가능합니다. 한편, memoryview 버전은 선형 복잡성을 가지며 번개처럼 빠릅니다.

편집 : 이것은 CPython에서 수행되었습니다. 메모리 뷰가 2 차 성능을 갖도록하는 최대 4.0.1의 Pypy 버그가있었습니다.


예제는 파이썬 3에서 작동하지 않습니다TypeError: memoryview: a bytes-like object is required, not 'str'
Calculus

@Jose printas a statement는 Python 3에서도 작동하지 않습니다.이 코드는 Python 2 용으로 작성되었지만 Python 3에 필요한 변경 사항은 상당히 사소합니다.
Antimony

@Yumi Tada, strpython3에서 python2에서 정의 된 것은 완전히 다릅니다.
hcnhcn012

3
아스 커 당신이 사용하는 바이트 () 복사 대상 ...에있는 상태로이 답변은 "유용한"아무것도 할 사실을 언급하지 않습니다
ragardner

1
@ citizen2077 내 예제에서 알 수 있듯이 궁극적으로 바이트 객체에 복사하더라도 중간 조작을 효율적으로 수행하는 데 유용합니다.
안티몬

58

memoryview인덱싱 만 지원하면되는 바이너리 데이터의 하위 집합이 필요할 때 개체가 유용합니다. 다른 API 로 전달하기 위해 조각을 가져 와서 (그리고 잠재적으로 큰 새 객체를 생성하는 대신) 객체를 가져갈 수 있습니다 memoryview.

이러한 API 예제 중 하나가 struct모듈입니다. bytes패킹 된 C 값을 구문 분석하기 위해 큰 개체 의 조각을 전달하는 대신 값 memoryview을 추출해야하는 영역 만 전달 합니다.

memoryview사실 객체는 struct기본적으로 압축 해제를 지원합니다 . bytes슬라이스를 사용 하여 기본 개체 의 영역을 대상으로 지정한 다음 .cast()기본 바이트를 긴 정수, 부동 소수점 값 또는 n 차원 정수 목록으로 '해석'하는 데 사용할 수 있습니다. 이렇게하면 바이트 복사본을 더 만들 필요없이 매우 효율적인 이진 파일 형식 해석이 가능합니다.


1
인덱싱 이상의 기능을 지원하는 하위 집합이 필요한 경우 어떻게해야합니까?!
Basel Shishani 2013 년

2
@BaselShishani : memoryview. 이진 데이터가 아닌 텍스트를 다루고 있습니다.
Martijn Pieters

예, 텍스트를 처리합니다. 그래서 우리는 memoryview를 사용하지 않습니다. 대안이 있습니까?
Basel Shishani 2013 년

해결하려는 문제는 무엇입니까? 테스트하는 데 필요한 부분 문자열이 그렇게 큰가요?
Martijn Pieters

6
@BaselShishani : memoryview를 슬라이스하면 해당 영역 만 포함하는 새로운 memoryview가 반환됩니다.
Martijn Pieters

4

여기서 이해의 결함이 어디에 있는지 명확히하겠습니다.

질문자는 저와 마찬가지로 기존 배열의 슬라이스 (예 : 바이트 또는 바이트 배열)를 선택하는 메모리 뷰를 만들 수있을 것으로 예상했습니다. 따라서 우리는 다음과 같은 것을 기대했습니다.

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에서 desired_slice_view를 가져와야한다고 예상 할 수있는 몇 가지 이유가 있습니다. 따라서 첫 번째 단계는 전체 기본 배열의 memoryview를 가져 오는 것입니다.

  2. start 및 end 인수가있는 memoryview 생성자의 순진한 기대는 슬라이스 사양이 실제로 일반적인 슬라이스 연산자 ([3 :: 2] 또는 [: -4] 등과 같은 것 포함)의 모든 표현성을 필요로한다고 생각하지 않습니다. 한 줄짜리 생성자에서 기존 (그리고 이해 된) 연산자를 사용할 방법이 없습니다. memoryview 생성자에게 일부 슬라이스 매개 변수를 알리는 대신 해당 배열의 슬라이스를 만들기 때문에이를 existing_array 인수에 연결할 수 없습니다. 연산자 자체는 값이나 객체가 아니라 연산자이기 때문에 인수로 사용할 수 없습니다.

상상할 수 있듯이 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

Antimony의 훌륭한 예입니다. 실제로 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.