Protocolo Búfer¶
Ciertos objetos disponibles en Python ajustan el acceso a un arreglo de memoria subyacente o buffer. Dichos objetos incluyen el incorporado bytes
y bytearray
, y algunos tipos de extensión como array.array
. Las bibliotecas de terceros pueden definir sus propios tipos para fines especiales, como el procesamiento de imágenes o el análisis numérico.
Si bien cada uno de estos tipos tiene su propia semántica, comparten la característica común de estar respaldados por un búfer de memoria posiblemente grande. Es deseable, en algunas situaciones, acceder a ese búfer directamente y sin copia intermedia.
Python proporciona una instalación de este tipo en el nivel C en la forma de protocolo búfer. Este protocolo tiene dos lados:
en el lado del productor, un tipo puede exportar una «interfaz de búfer» que permite a los objetos de ese tipo exponer información sobre su búfer subyacente. Esta interfaz se describe en la sección Estructuras de Objetos Búfer;
en el lado del consumidor, hay varios medios disponibles para obtener un puntero a los datos subyacentes sin procesar de un objeto (por ejemplo, un parámetro de método).
Los objetos simples como bytes
y bytearray
exponen su búfer subyacente en forma orientada a bytes. Otras formas son posibles; por ejemplo, los elementos expuestos por un array.array
pueden ser valores de varios bytes.
Un consumidor de ejemplo de la interfaz del búfer es el método write()
de objetos de archivo: cualquier objeto que pueda exportar una serie de bytes a través de la interfaz del búfer puede escribirse en un archivo. Mientras que write()
solo necesita acceso de solo lectura a los contenidos internos del objeto que se le pasa, otros métodos como readinto()
necesitan acceso de escritura a los contenidos de su argumento. La interfaz del búfer permite que los objetos permitan o rechacen selectivamente la exportación de búferes de lectura-escritura y solo lectura.
Hay dos formas para que un consumidor de la interfaz del búfer adquiera un búfer sobre un objeto de destino:
llamar
PyObject_GetBuffer()
con los parámetros correctos;llamar
PyArg_ParseTuple()
(o uno de sus hermanos) con uno de losy*
,w*
os*
códigos de formato.
En ambos casos, se debe llamar a PyBuffer_Release()
cuando ya no se necesita el búfer. De lo contrario, podrían surgir varios problemas, como pérdidas de recursos.
Estructura de búfer¶
Las estructuras de búfer (o simplemente «búferes») son útiles como una forma de exponer los datos binarios de otro objeto al programador de Python. También se pueden usar como un mecanismo de corte de copia cero. Usando su capacidad para hacer referencia a un bloque de memoria, es posible exponer cualquier información al programador Python con bastante facilidad. La memoria podría ser una matriz grande y constante en una extensión C, podría ser un bloque de memoria sin procesar para su manipulación antes de pasar a una biblioteca del sistema operativo, o podría usarse para pasar datos estructurados en su formato nativo en memoria .
Contrariamente a la mayoría de los tipos de datos expuestos por el intérprete de Python, los búferes no son punteros PyObject
sino estructuras C simples. Esto les permite ser creados y copiados de manera muy simple. Cuando se necesita un contenedor genérico alrededor de un búfer, un objeto memoryview puede ser creado.
Para obtener instrucciones breves sobre cómo escribir un objeto de exportación, consulte Estructuras de objetos búfer. Para obtener un búfer, consulte PyObject_GetBuffer()
.
-
Py_buffer
¶ -
void *
buf
¶ Un puntero al inicio de la estructura lógica descrita por los campos del búfer. Puede ser cualquier ubicación dentro del bloque de memoria física subyacente del exportador. Por ejemplo, con negativo
strides
el valor puede apuntar al final del bloque de memoria.Para arreglos contiguous, el valor apunta al comienzo del bloque de memoria.
-
void *
obj
¶ Una nueva referencia al objeto exportador. La referencia es propiedad del consumidor y automáticamente disminuye y se establece en
NULL
porPyBuffer_Release()
. El campo es el equivalente del valor de retorno de cualquier función estándar de C-API.Como un caso especial, para los búferes temporary que están envueltos por
PyMemoryView_FromBuffer()
oPyBuffer_FillInfo()
este campo esNULL
. En general, los objetos de exportación NO DEBEN usar este esquema.
-
Py_ssize_t
len
¶ product(shape) * itemize
. Para arreglos contiguos, esta es la longitud del bloque de memoria subyacente. Para arreglos no contiguos, es la longitud que tendría la estructura lógica si se copiara en una representación contigua.Accede a
((char *)buf)[0] hasta ((char *)buf)[len-1]
solo es válido si el búfer se ha obtenido mediante una solicitud que garantiza la contigüidad. En la mayoría de los casos, dicha solicitud seráPyBUF_SIMPLE
oPyBUF_WRITABLE
.
-
int
readonly
¶ Un indicador de si el búfer es de solo lectura. Este campo está controlado por el indicador
PyBUF_WRITABLE
.
-
Py_ssize_t
itemsize
¶ Tamaño del elemento en bytes de un solo elemento. Igual que el valor de
struct.calcsize()
invocado en valores noNULL
format
.Excepción importante: si un consumidor solicita un búfer sin el indicador
PyBUF_FORMAT
,format
se establecerá enNULL
, peroitemsize
todavía tiene el valor para el formato original.Si
shape
está presente, la igualdadproduct(shape) * itemsize == len
aún se mantiene y el consumidor puede usaritemsize
para navegar el búfer.Si
shape
esNULL
como resultado de unPyBUF_SIMPLE
o unPyBUF_WRITABLE
, el consumidor debe ignoraritemsize
y asumeitemsize == 1
.
-
const char *
format
¶ Una cadena de caracteres terminada en NUL en sintaxis de estilo del modulo
struct
que describe el contenido de un solo elemento. Si esto esNULL
, se supone"B"
(bytes sin signo).Este campo está controlado por el indicador
PyBUF_FORMAT
.
-
int
ndim
¶ El número de dimensiones que representa la memoria como un arreglo n-dimensional. Si es `` 0``,
buf
apunta a un solo elemento que representa un escalar. En este caso,shape
,strides
ysuboffsets
DEBE serNULL
.La macro
PyBUF_MAX_NDIM
limita el número máximo de dimensiones a 64. Los exportadores DEBEN respetar este límite, los consumidores de búfer multidimensionales DEBEN poder manejar hasta dimensionesPyBUF_MAX_NDIM
.
-
Py_ssize_t *
shape
¶ Un arreglo de
Py_ssize_t
de longitudndim
que indica la forma de la memoria como un arreglo n-dimensional. Tenga en cuenta queshape[0] * ... * shape[ndim-1] * itemsize
DEBE ser igual alen
.Los valores de forma están restringidos a
shape[n] >= 0
. El casoshape[n] == 0
requiere atención especial. Vea arreglos complejos (complex arrays) para más información.El arreglo de formas es de sólo lectura para el consumidor.
-
Py_ssize_t *
strides
¶ Un arreglo de
Py_ssize_t
de longitudndim
que proporciona el número de bytes que se omiten para llegar a un nuevo elemento en cada dimensión.Los valores de stride pueden ser cualquier número entero. Para los arreglos regulares, los pasos son generalmente positivos, pero un consumidor DEBE ser capaz de manejar el caso
strides[n] <= 0
. Ver complex arrays para más información.El arreglo strides es de sólo lectura para el consumidor.
-
Py_ssize_t *
suboffsets
¶ Un arreglo de
Py_ssize_t
de longitudndim
. Sisuboffsets[n] >= 0
, los valores almacenados a lo largo de la enésima dimensión son punteros y el valor del suboffsets dicta cuántos bytes agregar a cada puntero después de desreferenciarlos. Un valor de suboffsets negativo indica que no debe producirse una desreferenciación (striding en un bloque de memoria contiguo).Si todos los suboffsets son negativos (es decir, no se necesita desreferenciar), entonces este campo debe ser
NULL
(el valor predeterminado).Python Imaging Library (PIL) utiliza este tipo de representación de arreglos. Consulte complex arrays para obtener más información sobre cómo acceder a los elementos de dicho arreglo.
El arreglo de suboffsets es de sólo lectura para el consumidor.
-
void *
internal
¶ Esto es para uso interno del objeto exportador. Por ejemplo, el exportador podría volver a emitirlo como un número entero y utilizarlo para almacenar indicadores sobre si las matrices de forma, strides y suboffsets deben liberarse cuando se libera el búfer. El consumidor NO DEBE alterar este valor.
-
void *
Tipos de solicitud búfer¶
Los búferes obtienen generalmente enviando una solicitud de búfer a un objeto de exportación a través de PyObject_GetBuffer()
. Dado que la complejidad de la estructura lógica de la memoria puede variar drásticamente, el consumidor usa el argumento flags para especificar el tipo de búfer exacto que puede manejar.
Todos los campos Py_buffer
están definidos inequívocamente por el tipo de solicitud.
campos independientes de solicitud¶
Los siguientes campos no están influenciados por flags y siempre deben completarse con los valores correctos: obj
, buf
, len
, itemsize
, ndim
.
formato de sólo lectura¶
PyBUF_WRITABLE
¶Controla el campo
readonly
. Si se establece, el exportador DEBE proporcionar un búfer de escritura o, de lo contrario, informar de un error. De lo contrario, el exportador PUEDE proporcionar un búfer de solo lectura o de escritura, pero la elección DEBE ser coherente para todos los consumidores.
PyBUF_WRITABLE
puede ser |”d a cualquiera de las banderas en la siguiente sección. Dado que PyBUF_SIMPLE
se define como 0, PyBUF_WRITABLE
puede usarse como un indicador independiente para solicitar un búfer de escritura simple.
PyBUF_FORMAT
puede ser |”d para cualquiera de las banderas excepto PyBUF_SIMPLE
. Este último ya implica el formato B
(bytes sin signo).
formas, strides, suboffsets¶
Las banderas que controlan la estructura lógica de la memoria se enumeran en orden decreciente de complejidad. Tenga en cuenta que cada bandera contiene todos los bits de las banderas debajo de ella.
Solicitud |
forma |
strides |
suboffsets |
---|---|---|---|
|
sí |
sí |
si es necesario |
|
sí |
sí |
NULL |
|
sí |
NULL |
NULL |
|
NULL |
NULL |
NULL |
solicitudes de contigüidad¶
La contigüidad C o Fortran se puede solicitar explícitamente, con y sin información de paso. Sin información de paso, el búfer debe ser C-contiguo.
Solicitud |
forma |
strides |
suboffsets |
contig |
---|---|---|---|---|
|
sí |
sí |
NULL |
C |
|
sí |
sí |
NULL |
F |
|
sí |
sí |
NULL |
C o F |
sí |
NULL |
NULL |
C |
solicitudes compuestas¶
Todas las solicitudes posibles están completamente definidas por alguna combinación de las banderas en la sección anterior. Por conveniencia, el protocolo de memoria intermedia proporciona combinaciones de uso frecuente como indicadores únicos.
En la siguiente tabla U significa contigüidad indefinida. El consumidor tendría que llamar a PyBuffer_IsContiguous()
para determinar la contigüidad.
Solicitud |
forma |
strides |
suboffsets |
contig |
sólo lectura |
formato |
---|---|---|---|---|---|---|
|
sí |
sí |
si es necesario |
U |
0 |
sí |
|
sí |
sí |
si es necesario |
U |
1 o 0 |
sí |
|
sí |
sí |
NULL |
U |
0 |
sí |
|
sí |
sí |
NULL |
U |
1 o 0 |
sí |
|
sí |
sí |
NULL |
U |
0 |
NULL |
|
sí |
sí |
NULL |
U |
1 o 0 |
NULL |
|
sí |
NULL |
NULL |
C |
0 |
NULL |
|
sí |
NULL |
NULL |
C |
1 o 0 |
NULL |
Arreglos complejos¶
Estilo NumPy: forma y strides¶
La estructura lógica de las matrices de estilo NumPy está definida por itemsize
, ndim
, shape
y strides
.
Si ndim == 0
, la ubicación de memoria señalada por buf
se interpreta como un escalar de tamaño itemsize
. En ese caso, tanto shape
como strides
son NULL
.
Si strides
es NULL
, el arreglo se interpreta como un arreglo C n-dimensional estándar. De lo contrario, el consumidor debe acceder a un arreglo n-dimensional de la siguiente manera:
ptr = (char *)buf + indices[0] * strides[0] + ... + indices[n-1] * strides[n-1];
item = *((typeof(item) *)ptr);
Como se señaló anteriormente, buf
puede apuntar a cualquier ubicación dentro del bloque de memoria real. Un exportador puede verificar la validez de un búfer con esta función:
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, strides y suboffsets¶
Además de los elementos normales, los arreglos de estilo PIL pueden contener punteros que deben seguirse para llegar al siguiente elemento en una dimensión. Por ejemplo, el arreglo C tridimensional regular char v[2][2][3]
también se puede ver como un arreglo de 2 punteros a 2 arreglos bidimensionales: char (*v[2])[2][3]
. En la representación de suboffsets, esos dos punteros pueden incrustarse al comienzo de buf
, apuntando a dos matrices char x[2][3]
que pueden ubicarse en cualquier lugar de la memoria.
Aquí hay una función que retorna un puntero al elemento en un arreglo N-D a la que apunta un índice N-dimensional cuando hay strides y suboffsets no 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;
}