버퍼 프로토콜¶
파이썬에서 사용할 수 있는 어떤 객체는 하부 메모리 배열 또는 버퍼에 대한 액세스를 감쌉니다. 이러한 객체에는 내장 bytes 와 bytearray, 그리고 array.array와 같은 일부 확장형이 포함됩니다. 제삼자 라이브러리도 이미지 처리나 수치 해석과 같은 특수한 용도로 자체 형을 정의할 수 있습니다.
이러한 형은 각각 고유의 의미가 있지만, (아마도) 큰 메모리 버퍼에 의해 뒷받침되는 공통된 특징을 공유합니다. 어떤 상황에서는 중간 복사 없이 직접 버퍼에 액세스하는 것이 바람직합니다.
파이썬은 C 수준에서 버퍼 프로토콜 형식으로 이러한 기능을 제공합니다. 이 프로토콜에는 두 가지 측면이 있습니다:
- 생산자 측에서는, 형이 “버퍼 인터페이스”를 내보낼 수 있는데, 그 형의 객체가 하부 버퍼의 정보를 노출할 수 있게 합니다. 이 인터페이스는 Buffer Object Structures 절에서 설명됩니다. 
- 소비자 측에서는, 객체의 원시 하부 데이터에 대한 포인터를 얻기 위해 여러 가지 방법을 사용할 수 있습니다 (예를 들어 메서드 매개 변수). 
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¶
- A new reference to the exporting object. The reference is owned by the consumer and automatically decremented and set to - NULLby- PyBuffer_Release(). The field is the equivalent of the return value of any standard C-API function.- As a special case, for temporary buffers that are wrapped by - PyMemoryView_FromBuffer()or- PyBuffer_FillInfo()this field is- NULL. In general, exporting objects MUST NOT use this scheme.
 - 
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¶
- Item size in bytes of a single element. Same as the value of - struct.calcsize()called on non-- NULL- formatvalues.- Important exception: If a consumer requests a buffer without the - PyBUF_FORMATflag,- formatwill be set to- NULL, but- itemsizestill has the value for the original format.- shape이 있으면,- product(shape) * itemsize == len동치가 계속 성립하고 소비자는- itemsize를 사용하여 버퍼를 탐색할 수 있습니다.- If - shapeis- NULLas a result of a- PyBUF_SIMPLEor a- PyBUF_WRITABLErequest, the consumer must disregard- itemsizeand assume- itemsize == 1.
 - 
const char *format¶
- A NUL terminated string in - structmodule style syntax describing the contents of a single item. If this is- NULL,- "B"(unsigned bytes) is assumed.- 이 필드는 - PyBUF_FORMAT플래그로 제어됩니다.
 - 
int ndim¶
- The number of dimensions the memory represents as an n-dimensional array. If it is - 0,- bufpoints to a single item representing a scalar. In this case,- shape,- stridesand- suboffsetsMUST be- 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)가 발생하지 않아야 함을 나타냅니다 (연속 메모리 블록에서의 스트라이드).- If all suboffsets are negative (i.e. no de-referencing is needed), then this field must be - NULL(the default value).- 이 유형의 배열 표현은 파이썬 이미징 라이브러리(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로 정의됩니다.
If ndim == 0, the memory location pointed to by buf is
interpreted as a scalar of size itemsize. In that case,
both shape and strides are NULL.
If strides is NULL, the array is interpreted as
a standard n-dimensional C-array. Otherwise, the consumer must access an
n-dimensional array as follows:
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] 배열을 가리킵니다.
Here is a function that returns a pointer to the element in an N-D array
pointed to by an N-dimensional index when there are both non-NULL strides
and suboffsets:
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;
}
