3. Definiera utökningstyper: Blandade ämnen¶
Detta avsnitt syftar till att ge en snabb genomgång av de olika typmetoder som du kan implementera och vad de gör.
Här är definitionen av PyTypeObject
, med vissa fält som endast används i debug builds utelämnade:
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
Py_ssize_t tp_vectorcall_offset;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
PyAsyncMethods *tp_as_async; /* formerly known as tp_compare (Python 2)
or tp_reserved (Python 3) */
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
unsigned long tp_flags;
const char *tp_doc; /* Documentation string */
/* Assigned meaning in release 2.0 */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* Assigned meaning in release 2.1 */
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
Py_ssize_t tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
PyMethodDef *tp_methods;
PyMemberDef *tp_members;
PyGetSetDef *tp_getset;
// Strong reference on a heap type, borrowed reference on a static type
PyTypeObject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
Py_ssize_t tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache; /* no longer used */
void *tp_subclasses; /* for static builtin types this is an index */
PyObject *tp_weaklist; /* not used for static builtin types */
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6.
* If zero, the cache is invalid and must be initialized.
*/
unsigned int tp_version_tag;
destructor tp_finalize;
vectorcallfunc tp_vectorcall;
/* bitset of which type-watchers care about this type */
unsigned char tp_watched;
/* Number of tp_version_tag values used.
* Set to _Py_ATTR_CACHE_UNUSED if the attribute cache is
* disabled for this type (e.g. due to custom MRO entries).
* Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere).
*/
uint16_t tp_versions_used;
} PyTypeObject;
Nu är det en mängd metoder. Oroa dig dock inte för mycket - om du har en typ som du vill definiera är chansen mycket stor att du bara kommer att implementera en handfull av dessa.
Som du förmodligen förväntar dig vid det här laget kommer vi att gå igenom detta och ge mer information om de olika hanterarna. Vi kommer inte att gå igenom dem i den ordning de definieras i strukturen, eftersom det finns en hel del historiskt bagage som påverkar ordningen på fälten. Det är ofta enklast att hitta ett exempel som innehåller de fält du behöver och sedan ändra värdena så att de passar din nya typ:
const char *tp_name; /* För utskrift */
Typens namn - som nämndes i föregående kapitel kommer detta att visas på olika ställen, nästan uteslutande för diagnostiska ändamål. Försök att välja något som kan vara till hjälp i en sådan situation!
Py_ssize_t tp_basicsize, tp_itemsize; /* För allokering */
Dessa fält talar om för runtime hur mycket minne som ska allokeras när nya objekt av den här typen skapas. Python har en del inbyggt stöd för strukturer med variabel längd (tänk: strängar, tupler) och det är här fältet tp_itemsize
kommer in i bilden. Detta kommer att behandlas senare.
const char *tp_doc;
Här kan du lägga in en sträng (eller dess adress) som du vill ska returneras när Python-skriptet refererar till obj.__doc__
för att hämta doc-strängen.
Nu kommer vi till de grundläggande typmetoderna - de som de flesta extensionstyper kommer att implementera.
3.1. Slutförande och avyttring¶
destruktor tp_dealloc;
Denna funktion anropas när referensantalet för instansen av din typ har reducerats till noll och Python-tolken vill återta den. Om din typ har minne att frigöra eller annan upprensning att utföra, kan du lägga det här. Själva objektet måste också frigöras här. Här är ett exempel på denna funktion:
statiskt void
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
free(self->obj_UnderlyingDatatypePtr);
Py_TYPE(self)->tp_free(self);
}
Om din typ stöder sopsortering bör destruktorn anropa PyObject_GC_UnTrack()
innan den rensar alla medlemsfält:
statiskt void
newdatatype_dealloc(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
PyObject_GC_UnTrack(op);
Py_CLEAR(self->other_obj);
...
Py_TYPE(self)->tp_free(self);
}
Ett viktigt krav på deallocator-funktionen är att den lämnar alla väntande undantag i fred. Detta är viktigt eftersom deallokatorer ofta anropas när tolken rullar upp Python-stacken; när stacken rullas upp på grund av ett undantag (snarare än normala returer) görs ingenting för att skydda deallokatorerna från att se att ett undantag redan har ställts in. Alla åtgärder som en deallokator utför som kan orsaka att ytterligare Python-kod exekveras kan upptäcka att ett undantag har satts. Detta kan leda till missvisande fel från tolken. Det korrekta sättet att skydda sig mot detta är att spara ett väntande undantag innan man utför den osäkra åtgärden och återställa det när man är klar. Detta kan göras med hjälp av funktionerna PyErr_Fetch()
och PyErr_Restore()
:
static void
my_dealloc(PyObject *obj)
{
MyObject *self = (MyObject *) obj;
PyObject *cbresult;
if (self->my_callback != NULL) {
PyObject *err_type, *err_value, *err_traceback;
/* This saves the current exception state */
PyErr_Fetch(&err_type, &err_value, &err_traceback);
cbresult = PyObject_CallNoArgs(self->my_callback);
if (cbresult == NULL) {
PyErr_WriteUnraisable(self->my_callback);
}
else {
Py_DECREF(cbresult);
}
/* This restores the saved exception state */
PyErr_Restore(err_type, err_value, err_traceback);
Py_DECREF(self->my_callback);
}
Py_TYPE(self)->tp_free(self);
}
Anteckning
Det finns begränsningar för vad du säkert kan göra i en deallokeringsfunktion. För det första, om din typ stöder garbage collection (med tp_traverse
och/eller tp_clear
), kan några av objektets medlemmar ha rensats eller slutförts när tp_dealloc
anropas. För det andra, i tp_dealloc
är ditt objekt i ett instabilt tillstånd: dess referensantal är lika med noll. Varje anrop till ett icke-trivialt objekt eller API (som i exemplet ovan) kan sluta med att tp_dealloc
anropas igen, vilket orsakar en dubbel free och en krasch.
Från och med Python 3.4 rekommenderas det att inte lägga in någon komplex avslutningskod i tp_dealloc
, utan istället använda den nya typmetoden tp_finalize
.
Se även
PEP 442 förklarar det nya finaliseringsschemat.
3.2. Presentation av objekt¶
I Python finns det två sätt att generera en textuell representation av ett objekt: funktionen repr()
och funktionen str()
. (Funktionen print()
anropar bara str()
.) Dessa hanterare är båda valfria.
reprfunc tp_repr;
reprfunc tp_str;
Hanteraren tp_repr
bör returnera ett strängobjekt som innehåller en representation av den instans för vilken den anropas. Här är ett enkelt exempel:
statiskt PyObject *
newdatatype_repr(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
return PyUnicode_FromFormat("Repr-ified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
Om ingen tp_repr
-hanterare har angetts kommer tolken att tillhandahålla en representation som använder typens tp_name
och ett unikt identifierande värde för objektet.
Hanteraren tp_str
är för str()
vad hanteraren tp_repr
som beskrivs ovan är för repr()
; det vill säga den anropas när Python-kod anropar str()
på en instans av ditt objekt. Dess implementering är mycket lik tp_repr
-funktionen, men den resulterande strängen är avsedd för mänsklig konsumtion. Om tp_str
inte specificeras används istället tp_repr
.
Här är ett enkelt exempel:
statiskt PyObject *
newdatatype_str(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
return PyUnicode_FromFormat("Stringified_newdatatype{{size:%d}}",
self->obj_UnderlyingDatatypePtr->size);
}
3.3. Hantering av attribut¶
För varje objekt som kan stödja attribut måste motsvarande typ tillhandahålla de funktioner som styr hur attributen löses. Det måste finnas en funktion som kan hämta attribut (om några är definierade) och en annan för att ställa in attribut (om det är tillåtet att ställa in attribut). Att ta bort ett attribut är ett specialfall, för vilket det nya värdet som skickas till hanteraren är NULL
.
Python stöder två par attributhanterare; en typ som stöder attribut behöver bara implementera funktionerna för ett par. Skillnaden är att det ena paret tar namnet på attributet som ett char*, medan det andra accepterar ett PyObject*. Varje typ kan använda det par som är mest meningsfullt för implementeringens bekvämlighet.
getattrfunc tp_getattr; /* char * version */
setattrfunc tp_setattr;
/* ... */
getattrofunc tp_getattro; /* PyObject * version */
setattrofunc tp_setattro;
Om åtkomst till attribut för ett objekt alltid är en enkel operation (detta kommer att förklaras inom kort), finns det generiska implementationer som kan användas för att tillhandahålla PyObject*-versionen av attributhanteringsfunktionerna. Det faktiska behovet av typspecifika attributhanterare försvann nästan helt från och med Python 2.2, även om det finns många exempel som inte har uppdaterats för att använda några av de nya generiska mekanismer som finns tillgängliga.
3.3.1. Generisk attributhantering¶
De flesta tilläggstyper använder bara enkla attribut. Vad är det då som gör attributen enkla? Det finns bara ett par villkor som måste uppfyllas:
Namnet på attributen måste vara känt när
PyType_Ready()
anropas.Ingen särskild behandling behövs för att registrera att ett attribut har sökts upp eller ställts in, och inga åtgärder behöver vidtas baserat på värdet.
Observera att denna lista inte innehåller några restriktioner för attributens värden, när värdena beräknas eller hur relevanta data lagras.
När PyType_Ready()
anropas används tre tabeller som refereras av typobjektet för att skapa descriptor som placeras i typobjektets dictionary. Varje deskriptor kontrollerar åtkomst till ett attribut hos instansobjektet. Var och en av tabellerna är valfri; om alla tre är NULL
kommer instanser av typen endast att ha attribut som ärvs från deras bastyp, och bör lämna fälten tp_getattro
och tp_setattro
NULL
också, så att bastypen kan hantera attribut.
Tabellerna deklareras som tre fält av typen object:
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
Om tp_methods
inte är NULL
, måste den referera till en array av PyMethodDef
-strukturer. Varje post i tabellen är en instans av denna struktur:
typedef struct PyMethodDef {
const char *ml_name; /* metodens namn */
PyCFunction ml_meth; /* implementeringsfunktion */
int ml_flags; /* flaggor */
const char *ml_doc; /* dokumentsträng */
} PyMethodDef;
En post ska definieras för varje metod som tillhandahålls av typen; inga poster behövs för metoder som ärvs från en bastyp. Ytterligare en post behövs i slutet; det är en sentinel som markerar slutet på arrayen. Fältet ml_name
i sentineln måste vara NULL
.
Den andra tabellen används för att definiera attribut som är direkt kopplade till data som lagras i instansen. En mängd olika primitiva C-typer stöds och åtkomsten kan vara skrivskyddad eller skrivskyddad. Strukturerna i tabellen definieras enligt följande:
typedef struct PyMemberDef {
const char *namn;
int typ;
int offset;
int flaggor;
const char *doc;
} PyMemberDef;
För varje post i tabellen kommer en descriptor att konstrueras och läggas till typen som kommer att kunna extrahera ett värde från instansstrukturen. Fältet type
bör innehålla en typkod som Py_T_INT
eller Py_T_DOUBLE
; värdet kommer att användas för att bestämma hur Python-värden skall konverteras till och från C-värden. Fältet flags
används för att lagra flaggor som styr hur attributet kan nås: du kan sätta det till Py_READONLY
för att förhindra Python-kod från att sätta det.
En intressant fördel med att använda tabellen tp_members
för att skapa beskrivningar som används vid körning är att alla attribut som definieras på det här sättet kan ha en associerad doc-sträng genom att helt enkelt tillhandahålla texten i tabellen. Ett program kan använda introspektions-API:et för att hämta deskriptorn från klassobjektet och hämta dokumentsträngen med hjälp av attributet __doc__
.
Precis som i tabellen tp_methods
krävs en sentinel-post med värdet NULL
i ml_name
.
3.3.2. Typspecifik attributhantering¶
För enkelhetens skull kommer endast char*-versionen att demonstreras här; typen av namnparametern är den enda skillnaden mellan char*- och PyObject*-versionerna av gränssnittet. Det här exemplet gör i princip samma sak som det generiska exemplet ovan, men använder inte det generiska stöd som tillkom i Python 2.2. Det förklarar hur hanterarfunktionerna anropas, så att du förstår vad som behöver göras om du behöver utöka deras funktionalitet.
Hanteraren tp_getattr
anropas när objektet kräver en attributuppslagning. Den anropas i samma situationer som __getattr__()
-metoden för en klass skulle anropas.
Här är ett exempel:
statiskt PyObject *
newdatatype_getattr(PyObject *op, char *namn)
{
newdatatypeobject *self = (newdatatypeobject *) op;
if (strcmp(namn, "data") == 0) {
return PyLong_FromLong(self->data);
}
PyErr_Format(PyExc_AttributeError,
"'%.100s' objekt har inget attribut '%.400s'",
Py_TYPE(self)->tp_name, namn);
returnera NULL;
}
Hanteraren tp_setattr
anropas när metoden __setattr__()
eller __delattr__()
för en klassinstans skulle anropas. När ett attribut skall tas bort kommer den tredje parametern att vara NULL
. Här är ett exempel som helt enkelt ger upphov till ett undantag; om detta verkligen var allt du ville ha, skulle handläggaren tp_setattr
sättas till NULL
.
static int
newdatatype_setattr(PyObject *op, char *name, PyObject *v)
{
PyErr_Format(PyExc_RuntimeError, "Read-only attribute: %s", name);
return -1;
}
3.4. Jämförelse av objekt¶
richcmpfunc tp_richcompare;
Hanteraren tp_richcompare
anropas när jämförelser behövs. Den är analog med rich comparison methods, som __lt__()
, och anropas även av PyObject_RichCompare()
och PyObject_RichCompareBool()
.
Denna funktion anropas med två Python-objekt och operatorn som argument, där operatorn är en av Py_EQ
, Py_NE
, Py_LE
, Py_GE
, Py_LT
eller Py_GT
. Den bör jämföra de två objekten med avseende på den angivna operatorn och returnera Py_True
eller Py_False
om jämförelsen lyckas, Py_NotImplemented
för att indikera att jämförelsen inte är implementerad och att det andra objektets jämförelsemetod bör prövas, eller NULL
om ett undantag har angetts.
Här är ett exempel på implementering, för en datatyp som anses vara lika om storleken på en intern pekare är lika:
static PyObject *
newdatatype_richcmp(PyObject *lhs, PyObject *rhs, int op)
{
newdatatypeobject *obj1 = (newdatatypeobject *) lhs;
newdatatypeobject *obj2 = (newdatatypeobject *) rhs;
PyObject *result;
int c, size1, size2;
/* code to make sure that both arguments are of type
newdatatype omitted */
size1 = obj1->obj_UnderlyingDatatypePtr->size;
size2 = obj2->obj_UnderlyingDatatypePtr->size;
switch (op) {
case Py_LT: c = size1 < size2; break;
case Py_LE: c = size1 <= size2; break;
case Py_EQ: c = size1 == size2; break;
case Py_NE: c = size1 != size2; break;
case Py_GT: c = size1 > size2; break;
case Py_GE: c = size1 >= size2; break;
}
result = c ? Py_True : Py_False;
return Py_NewRef(result);
}
3.5. Stöd för abstrakta protokoll¶
Python stöder en mängd olika abstrakta ”protokoll”; de specifika gränssnitt som tillhandahålls för att använda dessa gränssnitt dokumenteras i Lager för abstrakta objekt.
Ett antal av dessa abstrakta gränssnitt definierades tidigt i utvecklingen av Python-implementeringen. I synnerhet har nummer-, mappnings- och sekvensprotokollen varit en del av Python sedan början. Andra protokoll har lagts till över tiden. För protokoll som är beroende av flera hanterarrutiner från typimplementationen har de äldre protokollen definierats som valfria block av hanterare som refereras av typobjektet. För nyare protokoll finns det ytterligare slots i huvudtypobjektet, med en flaggbit som sätts för att indikera att slots finns och bör kontrolleras av tolken. (Flaggbiten anger inte att slotvärdena är icke-NULL
. Flaggan kan vara satt för att ange att en slot finns, men en slot kan fortfarande vara ofylld)
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
Om du vill att ditt objekt ska kunna fungera som ett nummer, en sekvens eller ett mappningsobjekt, så placerar du adressen till en struktur som implementerar C-typen PyNumberMethods
, PySequenceMethods
, eller PyMappingMethods
, respektive. Det är upp till dig att fylla i denna struktur med lämpliga värden. Du kan hitta exempel på användningen av var och en av dessa i Objects
-katalogen i Python-källdistributionen.
hashfunc tp_hash;
Denna funktion, om du väljer att tillhandahålla den, ska returnera ett hashnummer för en instans av din datatyp. Här är ett enkelt exempel:
statisk Py_hash_t
newdatatype_hash(PyObject *op)
{
newdatatypeobject *self = (newdatatypeobject *) op;
Py_hash_t resultat;
resultat = self->some_size + 32767 * self->some_number;
if (resultat == -1) {
resultat = -2;
}
return result;
}
Py_hash_t
är en signerad heltalstyp med plattformsvarierande bredd. Att returnera -1
från tp_hash
indikerar ett fel, vilket är anledningen till att du bör vara noga med att undvika att returnera det när hashberäkningen lyckas, som ovan.
ternaryfunc tp_call;
Denna funktion anropas när en instans av din datatyp ”anropas”, t.ex. om obj1
är en instans av din datatyp och Python-skriptet innehåller obj1('hello')
, anropas hanteraren tp_call
.
Denna funktion tar tre argument:
self är instansen av den datatyp som är föremål för anropet. Om anropet är
obj1('hello')
, så är selfobj1
.args är en tupel som innehåller argumenten till anropet. Du kan använda
PyArg_ParseTuple()
för att extrahera argumenten.kwds är en ordbok med nyckelordsargument som skickades. Om detta är icke-
NULL
och du stöder nyckelordsargument, användPyArg_ParseTupleAndKeywords()
för att extrahera argumenten. Om du inte vill stödja nyckelordsargument och detta är icke-NULL
, skapa ettTypeError
med ett meddelande som säger att nyckelordsargument inte stöds.
Här är en leksaksimplementation av tp_call
:
statiskt PyObject *
newdatatype_call(PyObject *op, PyObject *args, PyObject *kwds)
{
newdatatypeobject *self = (newdatatypeobject *) op;
PyObject *resultat;
const char *arg1;
const char *arg2;
const char *arg3;
if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
returneras NULL;
}
result = PyUnicode_FromFormat(
"Återlämnar -- värde: [%d] arg1: [%s] arg2: [%s] arg3: [%s]\n",
self->obj_UnderliggandeDatatypPtr->storlek,
arg1, arg2, arg3);
returnera resultatet;
}
/* Iteratorer */
getiterfunc tp_iter;
iternextfunc tp_iternext;
Dessa funktioner ger stöd för iteratorprotokollet. Båda hanterarna tar exakt en parameter, den instans som de anropas för, och returnerar en ny referens. I händelse av ett fel bör de ange ett undantag och returnera NULL
. tp_iter
motsvarar Pythons __iter__()
-metod, medan tp_iternext
motsvarar Pythons __next__()
-metod.
Alla iterable-objekt måste implementera tp_iter
-hanteraren, som måste returnera ett iterator-objekt. Här gäller samma riktlinjer som för Python-klasser:
För samlingar (t.ex. listor och tupler) som kan stödja flera oberoende iteratorer bör en ny iterator skapas och returneras vid varje anrop till
tp_iter
.Objekt som bara kan itereras över en gång (vanligtvis på grund av bieffekter av iteration, t.ex. filobjekt) kan implementera
tp_iter
genom att returnera en ny referens till sig själva – och bör därför också implementeratp_iternext
-hanteraren.
Varje iterator-objekt bör implementera både tp_iter
och tp_iternext
. En iterators tp_iter
-hanterare bör returnera en ny referens till iteratorn. Dess tp_iternext
-hanterare bör returnera en ny referens till nästa objekt i iterationen, om det finns ett sådant. Om iterationen har nått slutet kan tp_iternext
returnera NULL
utan att ställa in ett undantag, eller så kan den ställa in StopIteration
i tillägg till att returnera NULL
; att undvika undantaget kan ge något bättre prestanda. Om ett faktiskt fel inträffar bör tp_iternext
alltid sätta ett undantag och returnera NULL
.
3.6. Svagt referensstöd¶
Ett av målen med Pythons implementering av svaga referenser är att låta alla typer delta i mekanismen för svaga referenser utan att det uppstår overhead på prestandakritiska objekt (t.ex. tal).
Se även
Dokumentation för modulen weakref
.
För att ett objekt ska vara svagt refererbart måste tilläggstypen sätta Py_TPFLAGS_MANAGED_WEAKREF
bit i fältet tp_flags
. Det äldre fältet tp_weaklistoffset
bör lämnas som noll.
Konkret ser det statiskt deklarerade typobjektet ut på följande sätt:
static PyTypeObject TrivialType = {
PyVarObject_HEAD_INIT(NULL, 0)
/* ... other members omitted for brevity ... */
.tp_flags = Py_TPFLAGS_MANAGED_WEAKREF | ...,
};
Det enda ytterligare tillägget är att tp_dealloc
måste rensa alla svaga referenser (genom att anropa PyObject_ClearWeakRefs()
):
static void
Trivial_dealloc(PyObject *op)
{
/* Clear weakrefs first before calling any destructors */
PyObject_ClearWeakRefs(op);
/* ... remainder of destruction code omitted for brevity ... */
Py_TYPE(op)->tp_free(op);
}
3.7. Fler förslag¶
För att lära dig hur du implementerar en specifik metod för din nya datatyp, hämta CPython källkod. Gå till katalogen Objects
och sök sedan i C-källfilerna efter tp_
plus den funktion du vill ha (t.ex. tp_richcompare
). Där hittar du exempel på den funktion som du vill implementera.
När du behöver verifiera att ett objekt är en konkret instans av den typ du implementerar, använd funktionen PyObject_TypeCheck()
. Ett exempel på dess användning kan vara något i stil med följande:
if (!PyObject_TypeCheck(some_object, &MyType)) {
PyErr_SetString(PyExc_TypeError, "arg #1 inte en mything");
returnera NULL;
}
Se även
- Ladda ner CPython källversioner.
- CPython-projektet på GitHub, där källkoden för CPython utvecklas.