버퍼 프로토콜
*************

파이썬에서 사용할 수 있는 어떤 객체는 하부 메모리 배열 또는 *버퍼*에
대한 액세스를 감쌉니다. 이러한 객체에는 내장 "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 배열을 해제해
      야 하는지에 대한 플래그를 저장하는 데 사용됩니다. 소비자가 이 값
      을 변경해서는 안 됩니다.


버퍼 요청 유형
==============

버퍼는 대개 "PyObject_GetBuffer()"를 통해 제공하는(exporting) 객체로
버퍼 요청을 보내서 얻습니다. 메모리의 논리적 구조의 복잡성이 크게 다를
수 있으므로, 소비자는 처리할 수 있는 정확한 버퍼 유형을 지정하기 위해
*flags* 인자를 사용합니다.

모든 "Py_buffer" 필드는 요청 유형에 의해 모호하지 않게 정의됩니다.


요청 독립적 필드
----------------

다음 필드는 *flags*의 영향을 받지 않고 항상 올바른 값으로 채워져야 합
니다: "obj", "buf", "len", "itemsize", "ndim".


readonly, format
----------------

   PyBUF_WRITABLE

      "readonly" 필드를 제어합니다. 설정되면, 제공자는 반드시 쓰기 가
      능한 버퍼를 제공하거나 실패를 보고해야 합니다. 그렇지 않으면, 제
      공자는 읽기 전용 버퍼나 쓰기 가능 버퍼를 제공할 수 있지만, 모든
      소비자에 대해 일관성을 유지해야 합니다.

   PyBUF_FORMAT

      "format" 필드를 제어합니다. 설정되면, 이 필드를 올바르게 채워야
      합니다. 그렇지 않으면, 이 필드는 반드시 "NULL" 이어야 합니다.

"PyBUF_WRITABLE"은 다음 섹션의 모든 플래그와 | 될 수 있습니다.
"PyBUF_SIMPLE"이 0으로 정의되므로, "PyBUF_WRITABLE"은 독립형 플래그로
사용되어 간단한 쓰기 가능한 버퍼를 요청할 수 있습니다.

"PyBUF_FORMAT"은 "PyBUF_SIMPLE"을 제외한 임의의 플래그와 | 될 수 있습
니다. PyBUF_SIMPLE은 이미 형식 "B"(부호 없는 바이트)를 의미합니다.


shape, strides, suboffsets
--------------------------

메모리의 논리 구조를 제어하는 플래그는 복잡도가 감소하는 순서로 나열됩
니다. 각 플래그는 그 아래에 있는 플래그의 모든 비트를 포함합니다.

+-------------------------------+---------+-----------+--------------+
| 요청                          | shape   | strides   | suboffsets   |
|===============================|=========|===========|==============|
| PyBUF_INDIRECT                | yes     | yes       | 필요하면     |
+-------------------------------+---------+-----------+--------------+
| PyBUF_STRIDES                 | yes     | yes       | NULL         |
+-------------------------------+---------+-----------+--------------+
| PyBUF_ND                      | yes     | NULL      | NULL         |
+-------------------------------+---------+-----------+--------------+
| PyBUF_SIMPLE                  | NULL    | NULL      | NULL         |
+-------------------------------+---------+-----------+--------------+


연속성 요청
-----------

C 나 포트란 *연속성*을 명시적으로 요청할 수 있는데, 스트라이드 정보를
포함하기도 그렇지 않기도 합니다. 스트라이드 정보가 없으면, 버퍼는 C-연
속이어야 합니다.

+-------------------------------------+---------+-----------+--------------+----------+
| 요청                                | shape   | strides   | suboffsets   | 연속성   |
|=====================================|=========|===========|==============|==========|
| PyBUF_C_CONTIGUOUS                  | yes     | yes       | NULL         | C        |
+-------------------------------------+---------+-----------+--------------+----------+
| PyBUF_F_CONTIGUOUS                  | yes     | yes       | NULL         | F        |
+-------------------------------------+---------+-----------+--------------+----------+
| PyBUF_ANY_CONTIGUOUS                | yes     | yes       | NULL         | C 또는 F |
+-------------------------------------+---------+-----------+--------------+----------+
| "PyBUF_ND"                          | yes     | NULL      | NULL         | C        |
+-------------------------------------+---------+-----------+--------------+----------+


복합 요청
---------

모든 가능한 요청은 앞 절의 플래그 조합에 의해 완전히 정의됩니다. 편의
상, 버퍼 프로토콜은 자주 사용되는 조합을 단일 플래그로 제공합니다.

다음 표에서 *U*는 정의되지 않은 연속성을 나타냅니다. 소비자는 연속성을
판단하기 위해 "PyBuffer_IsContiguous()"를 호출해야 합니다.

+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| 요청                            | shape   | strides   | suboffsets   | 연속성   | readonly   | format   |
|=================================|=========|===========|==============|==========|============|==========|
| PyBUF_FULL                      | yes     | yes       | 필요하면     | U        | 0          | yes      |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_FULL_RO                   | yes     | yes       | 필요하면     | U        | 1 또는 0   | yes      |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_RECORDS                   | yes     | yes       | NULL         | U        | 0          | yes      |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_RECORDS_RO                | yes     | yes       | NULL         | U        | 1 또는 0   | yes      |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_STRIDED                   | yes     | yes       | NULL         | U        | 0          | NULL     |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_STRIDED_RO                | yes     | yes       | NULL         | U        | 1 또는 0   | NULL     |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_CONTIG                    | yes     | NULL      | NULL         | C        | 0          | NULL     |
+---------------------------------+---------+-----------+--------------+----------+------------+----------+
| PyBUF_CONTIG_RO                 | 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;
   }


버퍼 관련 함수
==============

int PyObject_CheckBuffer(PyObject *obj)

   *obj*가 버퍼 인터페이스를 지원하면 "1"을 반환하고, 그렇지 않으면
   "0"을 반환합니다. "1"이 반환될 때, "PyObject_GetBuffer()"가 성공할
   것이라고 보장하지는 않습니다. 이 함수는 항상 성공합니다.

int PyObject_GetBuffer(PyObject *exporter, Py_buffer *view, int flags)

   *flags*에 지정된 대로 *view*를 채우도록 *exporter*에게 요청을 보냅
   니다. 제공자(exporter)가 정확한 유형의 버퍼를 제공할 수 없다면,
   "PyExc_BufferError"를 일으키고, "view->obj"를 "NULL"로 설정하고,
   "-1"를 반환해야 합니다.

   성공하면, *view*를 채우고, "view->obj"를 *exporter*에 대한 새 참조
   로 설정하고, 0을 반환합니다. 요청을 단일 객체로 리디렉션하는 연결된
   (chained) 버퍼 공급자의 경우, "view->obj"는 *exporter* 대신 이 객체
   를 참조할 수 있습니다 (버퍼 객체 구조체를 보세요).

   "PyObject_GetBuffer()"에 대한 성공적인 호출은 "PyBuffer_Release()"
   에 대한 호출과 쌍을 이루어야 합니다, "malloc()"과 "free()"와 유사합
   니다. 따라서, 소비자가 버퍼로 작업한 후에는, "PyBuffer_Release()"를
   정확히 한 번 호출해야 합니다.

void PyBuffer_Release(Py_buffer *view)

   버퍼 *view*를 해제하고 "view->obj"에 대한 참조 횟수를 감소시킵니다.
   버퍼가 더는 사용되지 않을 때, 이 함수를 반드시 호출해야 합니다. 그
   렇지 않으면 참조 누수가 발생할 수 있습니다.

   "PyObject_GetBuffer()"를 통해 얻지 않은 버퍼에 이 함수를 호출하는
   것은 에러입니다.

Py_ssize_t PyBuffer_SizeFromFormat(const char *format)

   "format"이 암시하는 "itemsize"를 반환합니다. 에러가 발생하면, 예외
   를 발생시키고 -1을 반환합니다.

   버전 3.9에 추가.

int PyBuffer_IsContiguous(Py_buffer *view, char order)

   *view*로 정의된 메모리가 C 스타일(*order*가 "'C'")이나 포트란 스타
   일(*order*가 "'F'") *연속*이거나 둘 중 하나(*order*가 "'A'")면 "1"
   을 반환합니다. 그렇지 않으면 "0"을 반환합니다. 이 함수는 항상 성공
   합니다.

void* PyBuffer_GetPointer(Py_buffer *view, Py_ssize_t *indices)

   주어진 *view* 내부의 *indices*가 가리키는 메모리 영역을 가져옵니다.
   *indices*는 "view->ndim" 인덱스의 배열을 가리켜야 합니다.

int PyBuffer_FromContiguous(Py_buffer *view, void *buf, Py_ssize_t len, char fort)

   *buf*에 있는 연속된 *len* 바이트를 *view*로 복사합니다. *fort*는
   "'C'" 또는 "'F'"(C 스타일 또는 포트란 스타일 순서)일 수 있습니다.
   성공하면 "0"이 반환되고, 에러가 있으면 "-1"이 반환됩니다.

int PyBuffer_ToContiguous(void *buf, Py_buffer *src, Py_ssize_t len, char order)

   *src*에 있는 *len* 바이트를 *buf*에 연속 표현으로 복사합니다.
   *order*는 "'C'" 또는 "'F'" 또는 "'A'"(C 스타일 또는 포트란 스타일
   순서 또는 둘 중 하나)일 수 있습니다. 성공하면 "0"이 반환되고, 에러
   가 있으면 "-1"이 반환됩니다.

   이 함수는 *len* != *src->len*이면 실패합니다.

void PyBuffer_FillContiguousStrides(int ndims, Py_ssize_t *shape, Py_ssize_t *strides, int itemsize, char order)

   *strides* 배열을 주어진 요소당 바이트 수와 주어진 shape 으로 *연속*
   (*order*가 "'C'"면 C 스타일, *order*가 "'F'"면 포트란 스타일) 배열
   의 바이트 스트라이드로 채웁니다.

int PyBuffer_FillInfo(Py_buffer *view, PyObject *exporter, void *buf, Py_ssize_t len, int readonly, int flags)

   *readonly*에 따라 쓰기 가능성이 설정된 *len* 크기의 *buf*를 노출하
   려는 제공자(exporter)에 대한 버퍼 요청을 처리합니다. *buf*는 부호
   없는 바이트의 시퀀스로 해석됩니다.

   *flags* 인자는 요청 유형을 나타냅니다. 이 함수는 *buf*가 읽기 전용
   으로 지정되고 "PyBUF_WRITABLE"이 *flags*에 설정되어 있지 않으면, 항
   상 플래그가 지정하는 대로 *view*를 채웁니다.

   성공하면, "view->obj"를 *exporter*에 대한 새 참조로 설정하고, 0을
   반환합니다. 그렇지 않으면, "PyExc_BufferError"를 일으키고,
   "view->obj"를 "NULL"로 설정한 다음 "-1"을 반환합니다.

   이 함수가 getbufferproc의 일부로 사용되면, *exporter*가 제공하는
   (exporting) 객체로 설정되어야 하고, *flags*는 수정되지 않은 채로 전
   달되어야 합니다. 그렇지 않으면 *exporter*가 "NULL"이어야 합니다.
