Protocolo de Buffer¶
Certos objetos disponíveis no Python envolvem o acesso a um vetor ou buffer de memória subjacente. Esses objetos incluem as bytes
e bytearray
embutidas, e alguns tipos de extensão como array.array
. As bibliotecas de terceiros podem definir seus próprios tipos para fins especiais, como processamento de imagem ou análise numérica.
Embora cada um desses tipos tenha sua própria semântica, eles compartilham a característica comum de serem suportados por um buffer de memória possivelmente grande. É desejável, em algumas situações, acessar esse buffer diretamente e sem cópia intermediária.
Python fornece essa facilidade no nível C sob a forma de protocolo de buffer. Este protocolo tem dois lados:
do lado do produtor, um tipo pode exportar uma “interface de buffer” que permite que objetos desse tipo exponham informações sobre o buffer subjacente. Esta interface é descrita na seção Buffer Object Structures;
do lado do consumidor, vários meios estão disponíveis para obter o ponteiro para os dados subjacentes de um objeto (por exemplo, um parâmetro de método).
Objetos simples como bytes
e bytearray
expõem seu buffer subjacente em uma forma orientada a byte. Outras formas são possíveis; por exemplo, os elementos expostos por uma array.array
podem ser valores de vários bytes.
Um exemplo de consumidor da interface de buffer é o método write()
de objetos arquivo: qualquer objeto que pode exportar uma série de bytes através da interface de buffer pode ser gravado em um arquivo. Enquanto write()
só precisa de acesso somente leitura aos conteúdos internos do objeto passado, outros métodos, tais como readinto()
precisam de acesso de gravação ao conteúdo de seu argumento. A interface de buffer permite aos objetos permitir ou rejeitar seletivamente a exportação de buffers de leitura e escrita e de somente leitura.
Existem duas maneiras para um consumidor da interface de buffer adquirir um buffer em um objeto alvo:
chamada de
PyObject_GetBuffer()
com os parâmetros certos;chamada de
PyArg_ParseTuple()
(ou um dos seus irmãos) com um dos códigos de formataçãoy*
,w*
ous*
.
Em ambos os casos, PyBuffer_Release()
deve ser chamado quando o buffer não é mais necessário. A falta de tal pode levar a várias questões, tais como vazamentos de recursos.
Estrutura de Buffer¶
As estruturas de buffer (ou simplesmente “buffers”) são úteis como uma maneira de expor os dados binários de outro objeto para o programador Python. Eles também podem ser usados como um mecanismo de cópia silenciosa. Usando sua capacidade de fazer referência a um bloco de memória, é possível expor facilmente qualquer dado ao programador Python. A memória pode ser uma matriz grande e constante em uma extensão C, pode ser um bloco bruto de memória para manipulação antes de passar para uma biblioteca do sistema operacional, ou pode ser usado para transmitir dados estruturados no formato nativo e formato de memória.
Ao contrário da maioria dos tipos de dados expostos pelo interpretador Python, os buffers não são ponteiros PyObject
mas sim estruturas C simples. Isso permite que eles sejam criados e copiados de forma muito simples. Quando um invólucro genérico em torno de um buffer é necessário, um objeto memoryview pode ser criado.
Para obter instruções curtas sobre como escrever um objeto exportador, consulte Buffer Object Structures. Para obter um buffer, veja PyObject_GetBuffer()
.
-
Py_buffer
¶ -
void *
buf
¶ Um ponteiro para o início da estrutura lógica descrita pelos campos do buffer. Este pode ser qualquer local dentro do bloco de memória física subjacente do exportador. Por exemplo, com negativo
strides
o valor pode apontar para o final do bloco de memória.Para vetores contíguos, o valor aponta para o início do bloco de memória.
-
void *
obj
¶ Uma nova referência ao objeto exportador. A referência é possuída pelo consumidor e automaticamente decrementada e definida para
NULL
porPyBuffer_Release()
. O campo é o equivalente ao valor de retorno de qualquer função padrão C-API.Como um caso especial, para buffers temporários que são encapsulados por
PyMemoryView_FromBuffer()
ouPyBuffer_FillInfo()
esse campo éNULL
. Em geral, objetos exportadores NÃO DEVEM usar esse esquema.
-
Py_ssize_t
len
¶ product(shape) * itemsize
. Para matrizes contíguas, este é o comprimento do bloco de memória subjacente. Para matrizes não contíguas, é o comprimento que a estrutura lógica teria se fosse copiado para uma representação contígua.Acessando
((char *)buf)[0] up to ((char *)buf)[len-1]
só é válido se o buffer tiver sido obtido por uma solicitação que garanta a contiguidade. Na maioria dos casos, esse pedido seráPyBUF_SIMPLE
ouPyBUF_WRITABLE
.
-
int
readonly
¶ Um indicador de se o buffer é somente leitura. Este campo é controlado pelo sinalizador
PyBUF_WRITABLE
.
-
Py_ssize_t
itemsize
¶ O tamanho do item em bytes de um único elemento. O mesmo que o valor de
struct.calcsize()
chamado em valores nãoNULL
deformat
.Exceção importante: Se um consumidor requisita um buffer sem sinalizador
PyBUF_FORMAT
,format
será definido comoNULL
, masitemsize
ainda terá seu valor para o formato original.Se
shape
está presente, a igualdadeproduct(shape) * itemsize == len
ainda é válida e o usuário pode usaritemsize
para navegar o buffer.Se
shape
éNULL
como resultado de umaPyBUF_SIMPLE
ou uma requisiçãoPyBUF_WRITABLE
, o consumidor deve ignoraritemsize
e presumiritemsize == 1
.
-
const char *
format
¶ Uma string terminada por NUL no estilo de sintaxe de módulo
struct
descrevendo os conteúdos de um único item. Se isso éNULL
,"B"
(unsigned bytes) é assumido.Este campo é controlado pelo sinalizador
PyBUF_FORMAT
.
-
int
ndim
¶ O número de dimensões que a memória representa como um vetor n-dimensional. Se é
0
,buf
aponta para um único item representando um escalar. Neste caso,shape
,strides
esuboffsets
DEVEM serNULL
.A macro
PyBUF_MAX_NDIM
limita o número máximo de dimensões a 64. Os exportadores DEVEM respeitar esse limite, os consumidores de buffers multidimensionais DEVEM ser capazes de lidar com dimensõesPyBUF_MAX_NDIM
.
-
Py_ssize_t *
shape
¶ Uma matriz de
Py_ssize_t
do comprimentondim
indicando a forma da memória como uma matriz n-dimensional. Observe que a formashape[0] * ... * shape[ndim-1] * itemsize
DEVE ser igual alen
.Os valores da forma são restritos a
shape[n] >= 0
. The caseshape[n] == 0
requer atenção especial. Veja complex arrays para mais informações.A forma de acesso a matriz é de somente leitura para o usuário.
-
Py_ssize_t *
strides
¶ Um vetor de
Py_ssize_t
de comprimentondim
dando o número de bytes para saltar para obter um novo elemento em cada dimensão.Os valores de Stride podem ser qualquer número inteiro. Para arrays regulares, os passos são geralmente positivos, mas um consumidor DEVE ser capaz de lidar com o caso
strides[n] <= 0
. Veja complex arrays para mais informações.A matriz de passos é somente leitura para o consumidor.
-
Py_ssize_t *
suboffsets
¶ Uma matriz de
Py_ssize_t
de comprimentondim
. Sesuboffsets[n] >= 0
, os valores armazenados ao longo da n-ésima dimensão são ponteiros e o valor suboffset determina quantos bytes para adicionar a cada ponteiro após desreferenciar. Um valor de suboffset que é negativo indica que não deve ocorrer desreferenciação (caminhando em um bloco de memória contíguo).Se todos os subconjuntos forem negativos (ou seja, não é necessário fazer referência), então este campo deve ser
NULL
(o valor padrão).Esse tipo de representação de matriz é usado pela Python Imaging Library (PIL). Veja complex arrays para obter mais informações sobre como acessar elementos dessa matriz.a matriz.
A matriz de subconjuntos é somente leitura para o consumidor.
-
void *
internal
¶ Isso é para uso interno pelo objeto exportador. Por exemplo, isso pode ser re-moldado como um número inteiro pelo exportador e usado para armazenar bandeiras sobre se os conjuntos de forma, passos e suboffsets devem ou não ser liberados quando o buffer é liberado. O consumidor NÃO DEVE alterar esse valor.
-
void *
Tipos de solicitação do buffer¶
Os buffers geralmente são obtidos enviando uma solicitação de buffer para um objeto exportador via PyObject_GetBuffer()
. Uma vez que a complexidade da estrutura lógica da memória pode variar drasticamente, o consumidor usa o argumento flags para especificar o tipo de buffer exato que pode manipular.
Todos Py_buffer
são inequivocamente definidos pelo tipo de solicitação.
campos independentes do pedido¶
Os seguintes campos não são influenciados por flags e devem sempre ser preenchidos com os valores corretos: obj
, buf
, len
, itemsize
, ndim
.
apenas em formato¶
:PyBUF_WRITABLE
pode ser |’d para qualquer um dos sinalizadores na próxima seção. Uma vez que PyBUF_WRITABLE
é definido como 0, PyBUF_WRITABLE
pode ser usado como uma bandeira autônoma para solicitar um buffer simples gravável.
PyBUF_FORMAT
pode ser |’d para qualquer um dos sinalizadores, exceto PyBUF_SIMPLE
. O último já implica o formato B
(bytes não assinados).
forma, avanços, suboffsets¶
As bandeiras que controlam a estrutura lógica da memória estão listadas em ordem decrescente de complexidade. Observe que cada bandeira contém todos os bits das bandeiras abaixo.
Solicitação |
Forma |
Avanços |
subconjuntos |
---|---|---|---|
|
sim |
sim |
se necessário |
|
sim |
sim |
NULL |
|
sim |
NULL |
NULL |
|
NULL |
NULL |
NULL |
requisições contíguas¶
contiguity do C ou Fortran podem ser explicitamente solicitadas, com ou sem informação de avanço. Sem informação de avanço, o buffer deve ser C-contíguo.
Solicitação |
Forma |
Avanços |
subconjuntos |
contig |
---|---|---|---|---|
|
sim |
sim |
NULL |
C |
|
sim |
sim |
NULL |
F |
|
sim |
sim |
NULL |
C ou F |
sim |
NULL |
NULL |
C |
requisições compostas¶
Todas as requisições possíveis foram completamente definidas por alguma combinação dos sinalizadores na seção anterior. Por conveniência, o protocolo do buffer fornece combinações frequentemente utilizadas como sinalizadores únicos.
Na seguinte tabela U significa contiguidade indefinida. O consumidor deve chamar PyBuffer_IsContiguous()
para determinar a contiguidade.
Solicitação |
Forma |
Avanços |
subconjuntos |
contig |
readonly |
formato |
---|---|---|---|---|---|---|
|
sim |
sim |
se necessário |
U |
0 |
sim |
|
sim |
sim |
se necessário |
U |
1 ou 0 |
sim |
|
sim |
sim |
NULL |
U |
0 |
sim |
|
sim |
sim |
NULL |
U |
1 ou 0 |
sim |
|
sim |
sim |
NULL |
U |
0 |
NULL |
|
sim |
sim |
NULL |
U |
1 ou 0 |
NULL |
|
sim |
NULL |
NULL |
C |
0 |
NULL |
|
sim |
NULL |
NULL |
C |
1 ou 0 |
NULL |
Vetores Complexos¶
Estilo NumPy: forma e avanços¶
A estrutura lógica de vetores do estilo NumPy é definida por itemsize
, ndim
, shape
e strides
.
Se ndim == 0
, a localização da memória apontada para buf
é interpretada como um escalar de tamanho itemsize
. Nesse caso, ambos shape
e strides
são NULL
.
Se strides
é NULL
, o vetor é interpretado como um vetor C n-dimensional padrão. Caso contrário, o consumidor deve acessar um vetor n-dimensional como a seguir:
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
Como notado acima, buf
pode apontar para qualquer localização dentro do bloco de memória em si. Um exportador pode verificar a validade de um buffer com essa função:
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
Estilo-PIL: forma, avanços e suboffsets¶
Além dos itens normais, uma matriz em estilo PIL pode conter ponteiros que devem ser seguidos para se obter o próximo elemento em uma dimensão. Por exemplo, a matriz tridimensional em C char v[2][2][3]
também pode ser vista como um vetor de 2 ponteiros para duas matrizes bidimensionais: char (*v[2])[2][3]
. Na representação por suboffsets, esses dois ponteiros podem ser embutidos no início de buf
, apontando para duas matrizes char x[2][3]
que podem estar localizadas em qualquer lugar na memória.
Esta é uma função que retorna um ponteiro para o elemento em uma matriz N-D apontada por um índice N-dimensional onde existem ambos passos e subconjuntos não-NULL
:
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;
}