Buffertprotokoll

Vissa objekt som finns i Python ger tillgång till en underliggande minnesarray eller buffer. Sådana objekt inkluderar de inbyggda bytes och bytearray, och vissa tilläggstyper som array.array. Tredjepartsbibliotek kan definiera sina egna typer för speciella ändamål, t.ex. bildbehandling eller numerisk analys.

Även om var och en av dessa typer har sin egen semantik, har de den gemensamma egenskapen att de backas upp av en eventuellt stor minnesbuffert. I vissa situationer är det då önskvärt att komma åt den bufferten direkt och utan mellanliggande kopiering.

Python tillhandahåller en sådan möjlighet på C- och Python-nivå i form av buffer protocol. Detta protokoll har två sidor:

  • på producentsidan kan en typ exportera ett ”buffertgränssnitt” som tillåter objekt av den typen att exponera information om sin underliggande buffert. Detta gränssnitt beskrivs i avsnittet Strukturer för buffertobjekt; för Python se Emulering av bufferttyper.

  • på konsumentsidan finns det flera sätt att få en pekare till de underliggande rådata för ett objekt (t.ex. en metodparameter). För Python se memoryview.

Enkla objekt som bytes och bytearray exponerar sin underliggande buffert i byteorienterad form. Andra former är möjliga; till exempel kan elementen som exponeras av en array.array vara multibyte-värden.

Ett exempel på en användare av buffertgränssnittet är metoden write() för filobjekt: alla objekt som kan exportera en serie byte genom buffertgränssnittet kan skrivas till en fil. Medan write() endast behöver skrivskyddad åtkomst till det interna innehållet i det objekt som skickas till den, behöver andra metoder som readinto() skrivåtkomst till innehållet i sitt argument. Buffertgränssnittet gör det möjligt för objekt att selektivt tillåta eller avvisa export av skrivläsningsbuffertar och skrivskyddade buffertar.

Det finns två sätt för en användare av buffertgränssnittet att skaffa en buffert över ett målobjekt:

I båda fallen måste PyBuffer_Release() anropas när bufferten inte längre behövs. Om detta inte görs kan det leda till olika problem, t.ex. resursläckage.

Tillagd i version 3.12: Buffertprotokollet är nu tillgängligt i Python, se Emulering av bufferttyper och memoryview.

Buffertstruktur

Buffertstrukturer (eller helt enkelt ”buffertar”) är användbara som ett sätt att exponera binärdata från ett annat objekt till Python-programmeraren. De kan också användas som en nollkopieringsmekanism. Genom att använda deras förmåga att referera till ett minnesblock är det möjligt att exponera vilken data som helst för Python-programmeraren ganska enkelt. Minnet kan vara en stor, konstant array i ett C-tillägg, det kan vara ett råminnesblock för manipulation innan det skickas till ett operativsystembibliotek, eller det kan användas för att skicka runt strukturerade data i sitt ursprungliga format i minnet.

Till skillnad från de flesta datatyper som exponeras av Python-tolken är buffertar inte PyObject-pekare utan snarare enkla C-strukturer. Detta gör att de kan skapas och kopieras mycket enkelt. När ett generiskt omslag runt en buffert behövs kan ett memoryview-objekt skapas.

För korta instruktioner om hur man skriver ett exporterande objekt, se Buffer Object Structures. För att hämta en buffert, se PyObject_GetBuffer().

type Py_buffer
En del av Stabil ABI (inklusive alla medlemmar) sedan version 3.11.
void *buf

En pekare till början av den logiska struktur som beskrivs av buffertfälten. Detta kan vara vilken plats som helst inom det underliggande fysiska minnesblocket hos exportören. Till exempel, med negativ strides kan värdet peka till slutet av minnesblocket.

För contiguous arrays pekar värdet på början av minnesblocket.

PyObject *obj

En ny referens till det exporterande objektet. Referensen ägs av konsumenten och frigörs automatiskt (d.v.s. referensantalet minskas) och sätts till NULL av PyBuffer_Release(). Fältet motsvarar returvärdet för vilken standard C-API-funktion som helst.

Som ett specialfall, för temporära buffertar som är omslutna av PyMemoryView_FromBuffer() eller PyBuffer_FillInfo() är detta fält NULL. Generellt gäller att exporterande objekt INTE får använda detta schema.

Py_ssize_t len

product(shape) * itemsize. För sammanhängande arrayer är detta längden på det underliggande minnesblocket. För icke sammanhängande matriser är det den längd som den logiska strukturen skulle ha om den kopierades till en sammanhängande representation.

Att komma åt ((char *)buf)[0] up to ((char *)buf)[len-1] är bara giltigt om bufferten har erhållits genom en begäran som garanterar sammanhängande. I de flesta fall kommer en sådan begäran att vara PyBUF_SIMPLE eller PyBUF_WRITABLE.

int readonly

En indikator på om bufferten är skrivskyddad. Detta fält styrs av PyBUF_WRITABLE flaggan.

Py_ssize_t itemsize

Elementstorlek i bytes för ett enda element. Samma som värdet av struct.calcsize() som anropas på icke-NULL format-värden.

Viktigt undantag: Om en konsument begär en buffert utan PyBUF_FORMAT flaggan, kommer format att sättas till NULL, men itemsize har fortfarande värdet för det ursprungliga formatet.

Om shape är närvarande gäller fortfarande likheten product(shape) * itemsize == len och konsumenten kan använda itemsize för att navigera i bufferten.

Om shape är NULL som ett resultat av en PyBUF_SIMPLE eller en PyBUF_WRITABLE begäran, måste konsumenten bortse från itemsize och anta itemsize == 1.

char *format

En NULL-terminerad sträng i struct-modulstilssyntax som beskriver innehållet i ett enda objekt. Om detta är NULL, antas "B" (osignerade bytes).

Detta fält styrs av flaggan PyBUF_FORMAT.

int ndim

Antalet dimensioner som minnet representerar som en n-dimensionell array. Om det är 0 pekar buf på ett enda objekt som representerar en skalär. I detta fall MÅSTE shape, strides och suboffsets vara NULL. Det maximala antalet dimensioner anges av PyBUF_MAX_NDIM.

Py_ssize_t *shape

En array av Py_ssize_t med längden ndim som anger formen på minnet som en n-dimensionell array. Observera att shape[0] * ... * shape[ndim-1] * itemsize MÅSTE vara lika med len.

Shape-värden är begränsade till shape[n] >= 0. Fallet shape[n] == 0 kräver särskild uppmärksamhet. Se komplexa matriser för ytterligare information.

Shape-arrayen är skrivskyddad för konsumenten.

Py_ssize_t *strides

En array av Py_ssize_t med längden ndim som anger antalet bytes som ska hoppas över för att komma till ett nytt element i varje dimension.

Stride-värden kan vara valfritt heltal. För vanliga arrayer är strides vanligtvis positiva, men en konsument MÅSTE kunna hantera fallet strides[n] <= 0. Se complex arrays för mer information.

Strides-arrayen är skrivskyddad för konsumenten.

Py_ssize_t *suboffsets

En array av Py_ssize_t med längden ndim. Om suboffsets[n] >= 0 är värdena som lagras längs den n:te dimensionen pekare och suboffset-värdet anger hur många byte som ska läggas till varje pekare efter de-referering. Ett negativt suboffset-värde anger att ingen de-referensering ska ske (striding i ett sammanhängande minnesblock).

Om alla suboffsets är negativa (dvs. ingen de-referensering behövs), måste detta fält vara NULL (standardvärdet).

Denna typ av matrisrepresentation används av Python Imaging Library (PIL). Se komplexa arrayer för mer information om hur du får tillgång till element i en sådan array.

Suboffsets-arrayen är skrivskyddad för konsumenten.

void *internal

Detta är för intern användning av det exporterande objektet. Det kan t.ex. omformas till ett heltal av exportören och användas för att lagra flaggor om huruvida arrayer med shape, strides och suboffsets måste frigöras när bufferten släpps. Konsumenten MÅSTE INTE ändra detta värde.

Konstanter:

PyBUF_MAX_NDIM

Det maximala antalet dimensioner som minnet representerar. Exportörer MÅSTE respektera denna gräns, konsumenter av flerdimensionella buffertar BÖR kunna hantera upp till PyBUF_MAX_NDIM dimensioner. För närvarande satt till 64.

Typer av buffertförfrågningar

Buffertar erhålls vanligtvis genom att skicka en buffertförfrågan till ett exporterande objekt via PyObject_GetBuffer(). Eftersom komplexiteten i minnets logiska struktur kan variera drastiskt använder konsumenten argumentet flags för att ange den exakta bufferttyp som den kan hantera.

Alla fält i Py_buffer definieras otvetydigt av typen av begäran.

fält oberoende av begäran

Följande fält påverkas inte av flags och måste alltid fyllas i med korrekta värden: obj, buf, len, itemsize, ndim.

skrivskyddad, format

PyBUF_WRITABLE

Kontrollerar fältet readonly. Om det är inställt MÅSTE exportören tillhandahålla en skrivbar buffert, annars rapporteras fel. I annat fall KAN exportören tillhandahålla antingen en skrivskyddad eller skrivbar buffert, men valet MÅSTE vara konsekvent för alla konsumenter. Till exempel kan PyBUF_SIMPLE | PyBUF_WRITABLE användas för att begära en enkel skrivbar buffert.

PyBUF_FORMAT

Kontrollerar fältet format. Om det är inställt MÅSTE fältet fyllas i korrekt. Annars MÅSTE detta fält vara NULL.

PyBUF_WRITABLE kan kopplas till någon av flaggorna i nästa avsnitt. Eftersom PyBUF_SIMPLE är definierad som 0, kan PyBUF_WRITABLE användas som en fristående flagga för att begära en enkel skrivbar buffert.

PyBUF_FORMAT måste vara |’d till någon av flaggorna utom PyBUF_SIMPLE, eftersom den senare redan implicerar format B (osignerade byte). PyBUF_FORMAT kan inte användas på egen hand.

form, strides, suboffsets

Flaggorna som styr minnets logiska struktur är listade i fallande ordning efter komplexitet. Observera att varje flagga innehåller alla bitar i flaggorna under den.

Förfrågan

form

steg

delmängder

PyBUF_INDIRECT

ja

ja

om det behövs

PyBUF_STRIDES

ja

ja

NULL

PyBUF_ND

ja

NULL

NULL

PyBUF_SIMPLE

NULL

NULL

NULL

begäran om angränsning

C eller Fortran contiguity kan uttryckligen begäras, med eller utan stride-information. Utan stride-information måste bufferten vara C-kontigu.

Förfrågan

form

steg

delmängder

kontigent

PyBUF_C_CONTIGUOUS

ja

ja

NULL

C

PyBUF_F_CONTIGUOUS

ja

ja

NULL

F

PyBUF_ANY_CONTIGUOUS

ja

ja

NULL

C eller F

PyBUF_ND

ja

NULL

NULL

C

sammansatta förfrågningar

Alla möjliga förfrågningar definieras fullt ut av någon kombination av flaggorna i föregående avsnitt. För enkelhetens skull tillhandahåller buffertprotokollet ofta använda kombinationer som enskilda flaggor.

I följande tabell står U för undefined contiguity (odefinierad sammanhängande). Konsumenten måste anropa PyBuffer_IsContiguous() för att avgöra sammanhängande.

Förfrågan

form

steg

delmängder

kontigent

skrivskyddad

format

PyBUF_FULL

ja

ja

om det behövs

U

0

ja

PyBUF_FULL_RO

ja

ja

om det behövs

U

1 eller 0

ja

PyBUF_RECORDS

ja

ja

NULL

U

0

ja

PyBUF_RECORDS_RO

ja

ja

NULL

U

1 eller 0

ja

PyBUF_STRIDED

ja

ja

NULL

U

0

NULL

PyBUF_STRIDED_RO

ja

ja

NULL

U

1 eller 0

NULL

PyBUF_CONTIG

ja

NULL

NULL

C

0

NULL

PyBUF_CONTIG_RO

ja

NULL

NULL

C

1 eller 0

NULL

Komplexa matriser

NumPy-stil: form och strides

Den logiska strukturen för matriser i NumPy-stil definieras av itemsize, ndim, shape och strides.

Om ndim == 0, tolkas minnesplatsen som pekas ut av buf som en skalär av storleken itemsize. I det fallet är både shape och strides NULL.

Om strides är NULL tolkas matrisen som en standard n-dimensionell C-array. Annars måste konsumenten komma åt en n-dimensionell array på följande sätt:

ptr = (char *)buf + index[0] * strides[0] + ... + index[n-1] * strides[n-1];
objekt = *((typeof(objekt) *)ptr);

Som nämnts ovan kan buf peka på vilken plats som helst inom det faktiska minnesblocket. En exportör kan kontrollera giltigheten av en buffert med denna funktion:

def verify_structure(memlen, itemsize, ndim, shape, strides, offset):
    """Verifiera att parametrarna representerar en giltig array inom
       gränserna för det allokerade minnet:
           char *mem: början på det fysiska minnesblocket
           memlen: längden på det fysiska minnesblocket
           offset: (char *)buf - mem
    """
    om offset % itemsize:
        returnera False
    om offset < 0 eller offset+itemsize > memlen:
        returnera False
    if any(v % itemsize för v i strides):
        returnera False

    om ndim <= 0:
        return ndim == 0 och inte shape och inte strides
    om 0 i shape:
        returnera True

    imin = sum(strides[j]*(shape[j]-1) for j in range(ndim)
               om strides[j] <= 0)
    imax = summa(strides[j]*(shape[j]-1) for j in range(ndim)
               om strides[j] > 0)

    return 0 <= offset+imin och offset+imax+itemsize <= memlen

PIL-stil: form, strides och suboffsets

Förutom de vanliga elementen kan matriser i PIL-stil innehålla pekare som måste följas för att komma till nästa element i en dimension. Till exempel kan den vanliga tredimensionella C-arrayen char v[2][2][3] också ses som en array med 2 pekare till 2 tvådimensionella arrayer: char (*v[2])[2][3]. I suboffsets-representationen kan dessa två pekare bäddas in i början av buf och peka på två char x[2][3] -arrayer som kan placeras var som helst i minnet.

Här är en funktion som returnerar en pekare till elementet i en N-D-array som pekas ut av ett N-dimensionellt index när det finns både icke-NULL strides och suboffsets:

void *get_item_pointer(int ndim, void *buf, Py_ssize_t *strides,
                       Py_ssize_t *strides, Py_ssize_t *suboffsets, Py_ssize_t *indices) {
    char *pointer = (char*)buf;
    int i;
    for (i = 0; i < ndim; i++) {
        pekare += strides[i] * index[i];
        om (suboffsets[i] >=0 ) {
            pekare = *((char**)pekare) + suboffsets[i];
        }
    }
    return (void*)pekare;
}