버퍼 프로토콜¶
파이썬에서 사용할 수 있는 어떤 객체는 하부 메모리 배열 또는 버퍼에 대한 액세스를 감쌉니다. 이러한 객체에는 내장 bytes
와 bytearray
, 그리고 array.array
와 같은 일부 확장형이 포함됩니다. 제삼자 라이브러리도 이미지 처리나 수치 해석과 같은 특수한 용도로 자체 형을 정의할 수 있습니다.
이러한 형은 각각 고유의 의미가 있지만, (아마도) 큰 메모리 버퍼에 의해 뒷받침되는 공통된 특징을 공유합니다. 어떤 상황에서는 중간 복사 없이 직접 버퍼에 액세스하는 것이 바람직합니다.
파이썬은 C 수준에서 버퍼 프로토콜 형식으로 이러한 기능을 제공합니다. 이 프로토콜에는 두 가지 측면이 있습니다:
생산자 측에서는, 형이 “버퍼 인터페이스”를 내보낼 수 있는데, 그 형의 객체가 하부 버퍼의 정보를 노출할 수 있게 합니다. 이 인터페이스는 버퍼 객체 구조체 절에서 설명됩니다.
소비자 측에서는, 객체의 원시 하부 데이터에 대한 포인터를 얻기 위해 여러 가지 방법을 사용할 수 있습니다 (예를 들어 메서드 매개 변수).
bytes
와 bytearray
와 같은 간단한 객체는 하부 버퍼를 바이트 지향 형식으로 노출합니다. 다른 형태도 가능합니다; 예를 들어, array.array
에 의해 노출되는 요소는 멀티 바이트 값이 될 수 있습니다.
버퍼 인터페이스의 소비자 예는 파일 객체의 write()
메서드입니다: 버퍼 인터페이스를 통해 일련의 바이트를 내보낼 수 있는 모든 객체는 파일에 기록될 수 있습니다. write()
가 전달된 객체의 내부 내용에 대한 읽기 전용 액세스만 필요하지만, readinto()
와 같은 다른 메서드는 인자의 내용에 쓰기 액세스가 필요합니다. 버퍼 인터페이스는 객체가 읽기-쓰기와 읽기 전용 버퍼를 선택적으로 허용하거나 거부할 수 있도록 합니다.
버퍼 인터페이스의 소비자가 대상 객체에 대해 버퍼를 얻는 방법에는 두 가지가 있습니다:
올바른 매개 변수로
PyObject_GetBuffer()
를 호출합니다;y*
,w*
또는s*
형식 코드 중 하나를 사용하여PyArg_ParseTuple()
(또는 그 형제 중 하나)을 호출합니다.
두 경우 모두, 버퍼가 더는 필요하지 않으면 PyBuffer_Release()
를 호출해야 합니다. 그렇게 하지 않으면 자원 누수와 같은 다양한 문제가 발생할 수 있습니다.
버퍼 구조체¶
버퍼 구조체(또는 단순히 “버퍼”)는 다른 객체의 바이너리 데이터를 파이썬 프로그래머에게 노출하는 방법으로 유용합니다. 또한, 복사 없는(zero-copy) 슬라이싱 메커니즘으로 사용할 수 있습니다. 메모리 블록을 참조하는 능력을 사용해서, 임의의 데이터를 파이썬 프로그래머에게 아주 쉽게 노출할 수 있습니다. 메모리는 C 확장의 큰 상수 배열일 수 있으며, 운영 체제 라이브러리로 전달되기 전에 조작하기 위한 원시 메모리 블록일 수도 있고, 네이티브 인 메모리(in-memory) 형식으로 구조화된 데이터를 전달하는 데 사용될 수도 있습니다.
파이썬 인터프리터가 노출하는 대부분의 데이터형과 달리, 버퍼는 PyObject
포인터가 아니라 단순한 C 구조체입니다. 이를 통해 매우 간단하게 만들고 복사할 수 있습니다. 버퍼를 감싸는 일반 래퍼가 필요할 때는, 메모리 뷰 객체를 만들 수 있습니다.
제공하는(exporting) 객체를 작성하는 간단한 지침은 버퍼 객체 구조체를 참조하십시오. 버퍼를 얻으려면, PyObject_GetBuffer()
를 참조하십시오.
-
Py_buffer
¶ -
void *
buf
¶ 버퍼 필드에 의해 기술된 논리적 구조의 시작을 가리키는 포인터. 이것은 제공자(exporter)의 하부 물리적 메모리 블록 내의 모든 위치일 수 있습니다. 예를 들어, 음의
strides
를 사용하면 값이 메모리 블록의 끝을 가리킬 수 있습니다.연속 배열의 경우, 값은 메모리 블록의 시작을 가리킵니다.
-
void *
obj
¶ 제공하는(exporting) 객체에 대한 새 참조. 참조는 소비자가 소유하고,
PyBuffer_Release()
에 의해 자동으로 감소하고NULL
로 설정됩니다. 이 필드는 표준 C-API 함수의 반환 값과 동등합니다.특수한 경우로,
PyMemoryView_FromBuffer()
나PyBuffer_FillInfo()
로 감싸진 임시(temporary) 버퍼의 경우, 이 필드는NULL
입니다. 일반적으로, 제공하는(exporting) 객체는 이 체계를 사용하지 않아야 합니다.
-
Py_ssize_t
len
¶ product(shape) * itemsize
. 연속 배열의 경우, 하부 메모리 블록의 길이입니다. 불연속 배열의 경우, 연속 표현으로 복사된다면 논리적 구조체가 갖게 될 길이입니다.((char *)buf)[0] 에서 ((char *)buf)[len-1]
범위의 액세스는 연속성을 보장하는 요청으로 버퍼가 확보된 경우에만 유효합니다. 대부분 이러한 요청은PyBUF_SIMPLE
또는PyBUF_WRITABLE
입니다.
-
int
readonly
¶ 버퍼가 읽기 전용인지를 나타내는 표시기입니다. 이 필드는
PyBUF_WRITABLE
플래그로 제어됩니다.
-
Py_ssize_t
itemsize
¶ 단일 요소의 항목 크기(바이트)입니다.
NULL
이 아닌format
값에 호출된struct.calcsize()
값과 같습니다.중요한 예외: 소비자가
PyBUF_FORMAT
플래그 없이 버퍼를 요청하면,format
은NULL
로 설정되지만,itemsize
는 여전히 원래 형식의 값을 갖습니다.shape
이 있으면,product(shape) * itemsize == len
동치가 계속 성립하고 소비자는itemsize
를 사용하여 버퍼를 탐색할 수 있습니다.PyBUF_SIMPLE
이나PyBUF_WRITABLE
요청의 결과로shape
이NULL
이면, 소비자는itemsize
를 무시하고itemsize == 1
로 가정해야 합니다.
-
const char *
format
¶ 단일 항목의 내용을 설명하는
struct
모듈 스타일 문법의 NUL 종료 문자열. 이것이NULL
이면,"B"
(부호 없는 바이트)를 가정합니다.이 필드는
PyBUF_FORMAT
플래그로 제어됩니다.
-
int
ndim
¶ 메모리가 n 차원 배열로 나타내는 차원 수.
0
이면,buf
는 스칼라를 나타내는 단일 항목을 가리 킵니다. 이 경우,shape
,strides
및suboffsets
는 반드시NULL
이어야 합니다.매크로
PyBUF_MAX_NDIM
는 최대 차원 수를 64로 제한합니다. 제공자는 이 제한을 존중해야 하며, 다차원 버퍼의 소비자는PyBUF_MAX_NDIM
차원까지 처리할 수 있어야 합니다.
-
Py_ssize_t *
shape
¶ n-차원 배열로 메모리의 모양을 나타내는 길이
ndim
의Py_ssize_t
배열.shape[0] * ... * shape[ndim-1] * itemsize
는len
과 같아야 합니다.모양 값은
shape[n] >= 0
로 제한됩니다.shape[n] == 0
인 경우는 특별한 주의가 필요합니다. 자세한 정보는 복잡한 배열을 참조하십시오.shape 배열은 소비자에게 읽기 전용입니다.
-
Py_ssize_t *
strides
¶ 각 차원에서 새 요소를 가져오기 위해 건너뛸 바이트 수를 제공하는 길이
ndim
의Py_ssize_t
배열.스트라이드 값은 임의의 정수일 수 있습니다. 일반 배열의 경우, 스트라이드는 보통 양수이지만, 소비자는
strides[n] <= 0
인 경우를 처리할 수 있어야 합니다. 자세한 내용은 복잡한 배열을 참조하십시오.strides 배열은 소비자에게 읽기 전용입니다.
-
Py_ssize_t *
suboffsets
¶ 길이
ndim
의Py_ssize_t
배열.suboffsets[n] >= 0
면, n 번째 차원을 따라 저장된 값은 포인터이고 서브 오프셋 값은 역참조(de-referencing) 후 각 포인터에 더할 바이트 수를 나타냅니다. 음의 서브 오프셋 값은 역참조(de-referencing)가 발생하지 않아야 함을 나타냅니다 (연속 메모리 블록에서의 스트라이드).모든 서브 오프셋이 음수면 (즉, 역참조가 필요하지 않으면), 이 필드는
NULL
(기본값) 이어야 합니다.이 유형의 배열 표현은 파이썬 이미징 라이브러리(PIL)에서 사용됩니다. 이러한 배열 요소에 액세스하는 방법에 대한 자세한 내용은 복잡한 배열을 참조하십시오.
suboffsets 배열은 소비자에게 읽기 전용입니다.
-
void *
internal
¶ 이것은 제공하는(exporting) 객체에 의해 내부적으로 사용됩니다. 예를 들어, 이것은 제공자(exporter)가 정수로 다시 캐스팅할 수 있으며, 버퍼가 해제될 때 shape, strides 및 suboffsets 배열을 해제해야 하는지에 대한 플래그를 저장하는 데 사용됩니다. 소비자가 이 값을 변경해서는 안 됩니다.
-
void *
버퍼 요청 유형¶
버퍼는 대개 PyObject_GetBuffer()
를 통해 제공하는(exporting) 객체로 버퍼 요청을 보내서 얻습니다. 메모리의 논리적 구조의 복잡성이 크게 다를 수 있으므로, 소비자는 처리할 수 있는 정확한 버퍼 유형을 지정하기 위해 flags 인자를 사용합니다.
모든 Py_buffer
필드는 요청 유형에 의해 모호하지 않게 정의됩니다.
요청 독립적 필드¶
다음 필드는 flags의 영향을 받지 않고 항상 올바른 값으로 채워져야 합니다: obj
, buf
, len
, itemsize
, ndim
.
readonly, format¶
PyBUF_WRITABLE
은 다음 섹션의 모든 플래그와 | 될 수 있습니다. PyBUF_SIMPLE
이 0으로 정의되므로, PyBUF_WRITABLE
은 독립형 플래그로 사용되어 간단한 쓰기 가능한 버퍼를 요청할 수 있습니다.
PyBUF_FORMAT
은 PyBUF_SIMPLE
을 제외한 임의의 플래그와 | 될 수 있습니다. PyBUF_SIMPLE은 이미 형식 B
(부호 없는 바이트)를 의미합니다.
shape, strides, suboffsets¶
메모리의 논리 구조를 제어하는 플래그는 복잡도가 감소하는 순서로 나열됩니다. 각 플래그는 그 아래에 있는 플래그의 모든 비트를 포함합니다.
요청 |
shape |
strides |
suboffsets |
---|---|---|---|
|
yes |
yes |
필요하면 |
|
yes |
yes |
NULL |
|
yes |
NULL |
NULL |
|
NULL |
NULL |
NULL |
연속성 요청¶
C 나 포트란 연속성을 명시적으로 요청할 수 있는데, 스트라이드 정보를 포함하기도 그렇지 않기도 합니다. 스트라이드 정보가 없으면, 버퍼는 C-연속이어야 합니다.
요청 |
shape |
strides |
suboffsets |
연속성 |
---|---|---|---|---|
|
yes |
yes |
NULL |
C |
|
yes |
yes |
NULL |
F |
|
yes |
yes |
NULL |
C 또는 F |
yes |
NULL |
NULL |
C |
복합 요청¶
모든 가능한 요청은 앞 절의 플래그 조합에 의해 완전히 정의됩니다. 편의상, 버퍼 프로토콜은 자주 사용되는 조합을 단일 플래그로 제공합니다.
다음 표에서 U는 정의되지 않은 연속성을 나타냅니다. 소비자는 연속성을 판단하기 위해 PyBuffer_IsContiguous()
를 호출해야 합니다.
요청 |
shape |
strides |
suboffsets |
연속성 |
readonly |
format |
---|---|---|---|---|---|---|
|
yes |
yes |
필요하면 |
U |
0 |
yes |
|
yes |
yes |
필요하면 |
U |
1 또는 0 |
yes |
|
yes |
yes |
NULL |
U |
0 |
yes |
|
yes |
yes |
NULL |
U |
1 또는 0 |
yes |
|
yes |
yes |
NULL |
U |
0 |
NULL |
|
yes |
yes |
NULL |
U |
1 또는 0 |
NULL |
|
yes |
NULL |
NULL |
C |
0 |
NULL |
|
yes |
NULL |
NULL |
C |
1 또는 0 |
NULL |
복잡한 배열¶
NumPy-스타일: shape과 strides¶
NumPy 스타일 배열의 논리적 구조는 itemsize
, ndim
, shape
및 strides
로 정의됩니다.
ndim == 0
이면, buf
가 가리키는 메모리 위치가 itemsize
크기의 스칼라로 해석됩니다. 이 경우, shape
과 strides
는 모두 NULL
입니다.
strides
가 NULL
이면, 배열은 표준 n-차원 C 배열로 해석됩니다. 그렇지 않으면, 소비자는 다음과 같이 n-차원 배열에 액세스해야 합니다:
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
위에서 언급했듯이, buf
는 실제 메모리 블록 내의 모든 위치를 가리킬 수 있습니다. 제공자(exporter)는 이 함수로 버퍼의 유효성을 검사 할 수 있습니다:
def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
"""Verify that the parameters represent a valid array within
the bounds of the allocated memory:
char *mem: start of the physical memory block
memlen: length of the physical memory block
offset: (char *)buf - mem
"""
if offset % itemsize:
return False
if offset < 0 or offset+itemsize > memlen:
return False
if any(v % itemsize for v in strides):
return False
if ndim <= 0:
return ndim == 0 and not shape and not strides
if 0 in shape:
return True
imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] <= 0)
imax = sum(strides[j]*(shape[j]-1) for j in range(ndim)
if strides[j] > 0)
return 0 <= offset+imin and offset+imax+itemsize <= memlen
PIL-스타일: shape, strides 및 suboffsets¶
일반 항목 외에도, PIL 스타일 배열에는 차원의 다음 요소를 가져오기 위해 따라야 하는 포인터가 포함될 수 있습니다. 예를 들어, 일반 3-차원 C 배열 char v[2][2][3]
는 2개의 2-차원 배열을 가리키는 2개의 포인터 배열로 볼 수도 있습니다: char (*v[2])[2][3]
. suboffsets 표현에서, 이 두 포인터는 buf
의 시작 부분에 임베드 될 수 있는데, 메모리의 어느 위치 에나 배치될 수 있는 두 개의 char x[2][3]
배열을 가리킵니다.
다음은 NULL
이 아닌 strides와 suboffsets가 있을 때, N-차원 인덱스가 가리키는 N-차원 배열의 요소에 대한 포인터를 반환하는 함수입니다:
void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
Py_ssize_t *suboffsets, Py_ssize_t *indices) {
char *pointer = (char*)buf;
int i;
for (i = 0; i < ndim; i++) {
pointer += strides[i] * indices[i];
if (suboffsets[i] >=0 ) {
pointer = *((char**)pointer) + suboffsets[i];
}
}
return (void*)pointer;
}