1111 other files, you'll have to create a file "foobarobject.h"; see
1212 floatobject.h for an example.
1313
14- This module roughly corresponds to::
14+ This module uses Limited API 3.15.
15+ See ``xxlimited_3_13.c`` if you want to support older CPython versions.
16+
17+ This module roughly corresponds to the following.
18+ (All underscore-prefixed attributes are not accessible from Python.)
19+
20+ ::
1521
1622 class Xxo:
1723 """A class that explicitly stores attributes in an internal dict
2733 return self._x_attr[name]
2834
2935 def __setattr__(self, name, value):
36+ if name == "reserved":
37+ raise AttributeError("cannot set 'reserved'")
3038 self._x_attr[name] = value
3139
3240 def __delattr__(self, name):
6876#define Py_LIMITED_API 0x030f0000
6977
7078// experimental: free-threaded build compatibility
79+ // (for internal tests; this should only appear here in CPython alpha builds)
7180#define _Py_OPAQUE_PYOBJECT 0x030f0000
7281
82+
7383#include "Python.h"
7484#include <string.h>
7585
@@ -82,11 +92,49 @@ typedef struct {
8292} xx_state ;
8393
8494
85- /* Xxo objects */
95+ /* Xxo objects.
96+ *
97+ * A non-trivial extension type, intentionally showing a number of features
98+ * that aren't easy to implement in the Limited API.
99+ */
86100
101+ // Forward declaration
87102static PyType_Spec Xxo_Type_spec ;
88103
89- // Instance state
104+ // Get the module state (xx_state*) from a given type object 'type', which
105+ // must be a subclass of Xxo (the type we're defining).
106+ // This is complicated by the fact that the Xxo type is dynamically allocated,
107+ // and there may be several such types in a given Python process -- for
108+ // example, in different subinterpreters, or through loading this
109+ // extension module several times.
110+ // So, we don't have a "global" pointer to the type, or to the module, etc.;
111+ // instead we search based on `Xxo_Type_spec` (which is static, immutable,
112+ // and process-global).
113+ //
114+ // When possible, it's better to avoid `PyType_GetBaseByToken` -- for an
115+ // example, see the `demo` method (Xxo_demo C function), which uses a
116+ // "defining class". But, in many cases it's the best solution.
117+ static xx_state *
118+ Xxo_state_from_type (PyTypeObject * type )
119+ {
120+ PyTypeObject * base ;
121+ // Search all superclasses of 'type' for one that was defined using
122+ // "Xxo_Type_spec". That must be our 'Xxo' class.
123+ if (PyType_GetBaseByToken (type , & Xxo_Type_spec , & base ) < 0 ) {
124+ return NULL ;
125+ }
126+ if (base == NULL ) {
127+ PyErr_SetString (PyExc_TypeError , "need Xxo subclass" );
128+ return NULL ;
129+ }
130+ // From this type, get the associated module. That must be the
131+ // relevant `xxlimited` module.
132+ xx_state * state = PyType_GetModuleState (base );
133+ Py_DECREF (base );
134+ return state ;
135+ }
136+
137+ // Structure for data needed by the XxoObject type.
90138typedef struct {
91139 PyObject * x_attr ; /* Attributes dictionary.
92140 * May be NULL, which acts as an
@@ -96,23 +144,41 @@ typedef struct {
96144 Py_ssize_t x_exports ; /* how many buffer are exported */
97145} XxoObject_Data ;
98146
99- #define XxoObject_CAST (self ) ((XxoObject *)(self))
100- // TODO: full support for type-checking was added in 3.14 (Py_tp_token)
101- // #define XxoObject_Check(v) Py_IS_TYPE(v, Xxo_Type)
147+ // Get the `XxoObject_Data` structure for a given instance of our type.
148+ static XxoObject_Data *
149+ Xxo_get_data (PyObject * self )
150+ {
151+ xx_state * state = Xxo_state_from_type (Py_TYPE (self ));
152+ if (!state ) {
153+ return NULL ;
154+ }
155+ XxoObject_Data * data = PyObject_GetTypeData (self , state -> Xxo_Type );
156+ return data ;
157+ }
102158
159+ // Xxo initialization
160+ // This is the implementation of Xxo.__new__; it takes arbitrary positional
161+ // and keyword arguments.
103162static PyObject *
104- newXxoObject (PyObject * module )
105- {
106- xx_state * state = PyModule_GetState (module );
107- if (state == NULL ) {
163+ Xxo_new (PyTypeObject * type , PyObject * args , PyObject * kwargs ) {
164+ // Validate that we did not get any arguments.
165+ if ((args != NULL && PyObject_Length (args ))
166+ || (kwargs != NULL && PyObject_Length (kwargs )))
167+ {
168+ PyErr_SetString (PyExc_TypeError , "Xxo.__new__() takes no arguments" );
108169 return NULL ;
109170 }
110- allocfunc alloc = PyType_GetSlot (state -> Xxo_Type , Py_tp_alloc );
111- PyObject * self = alloc (state -> Xxo_Type , 0 );
171+ // Create an instance of *type* (which may be a subclass)
172+ allocfunc alloc = PyType_GetSlot (type , Py_tp_alloc );
173+ PyObject * self = alloc (type , 0 );
112174 if (self == NULL ) {
113175 return NULL ;
114176 }
115- XxoObject_Data * xxo_data = PyObject_GetTypeData (self , state -> Xxo_Type );
177+
178+ // Initialize the C members on the instance.
179+ // This is only included for the sake of example. The default alloc
180+ // function zeroes instance memory; we don't need to do it again.
181+ XxoObject_Data * xxo_data = Xxo_get_data (self );
116182 if (xxo_data == NULL ) {
117183 return NULL ;
118184 }
@@ -122,33 +188,6 @@ newXxoObject(PyObject *module)
122188 return self ;
123189}
124190
125- static xx_state *
126- Xxo_state_from_type (PyTypeObject * type , void * token )
127- {
128- PyTypeObject * base ;
129- if (PyType_GetBaseByToken (type , & Xxo_Type_spec , & base ) < 0 ) {
130- return NULL ;
131- }
132- if (base == NULL ) {
133- return NULL ;
134- }
135- xx_state * state = PyType_GetModuleState (base );
136- return state ;
137- }
138-
139- #include <stdio.h>
140-
141- static XxoObject_Data *
142- Xxo_get_data (PyObject * self )
143- {
144- xx_state * state = Xxo_state_from_type (Py_TYPE (self ), & Xxo_Type_spec );
145- if (!state ) {
146- return NULL ;
147- }
148- XxoObject_Data * data = PyObject_GetTypeData (self , state -> Xxo_Type );
149- return data ;
150- }
151-
152191/* Xxo finalization.
153192 *
154193 * Types that store references to other PyObjects generally need to implement
@@ -236,6 +275,16 @@ Xxo_getattro(PyObject *self, PyObject *name)
236275static int
237276Xxo_setattro (PyObject * self , PyObject * name , PyObject * v )
238277{
278+ // filter a specific attribute name
279+ int is_reserved = PyUnicode_EqualToUTF8 (name , "reserved" );
280+ if (is_reserved < 0 ) {
281+ return -1 ;
282+ }
283+ else if (is_reserved ) {
284+ PyErr_Format (PyExc_AttributeError , "cannot set %R" , name );
285+ return -1 ;
286+ }
287+
239288 XxoObject_Data * data = Xxo_get_data (self );
240289 if (data == NULL ) {
241290 return -1 ;
@@ -355,6 +404,7 @@ static PyGetSetDef Xxo_getsetlist[] = {
355404
356405static PyType_Slot Xxo_Type_slots [] = {
357406 {Py_tp_doc , (char * )Xxo_doc },
407+ {Py_tp_new , Xxo_new },
358408 {Py_tp_traverse , Xxo_traverse },
359409 {Py_tp_clear , Xxo_clear },
360410 {Py_tp_finalize , Xxo_finalize },
@@ -372,7 +422,7 @@ static PyType_Slot Xxo_Type_slots[] = {
372422static PyType_Spec Xxo_Type_spec = {
373423 .name = "xxlimited.Xxo" ,
374424 .basicsize = - (Py_ssize_t )sizeof (XxoObject_Data ),
375- .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC ,
425+ .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE ,
376426 .slots = Xxo_Type_slots ,
377427};
378428
@@ -419,7 +469,9 @@ xx_foo(PyObject *module, PyObject *args)
419469static PyObject *
420470xx_new (PyObject * module , PyObject * Py_UNUSED (unused ))
421471{
422- return newXxoObject (module );
472+ xx_state * state = PyModule_GetState (module );
473+
474+ return Xxo_new (state -> Xxo_Type , NULL , NULL );
423475}
424476
425477
@@ -538,6 +590,9 @@ static PyModuleDef_Slot xx_slots[] = {
538590 * Without this slot, free-threaded builds of CPython will enable
539591 * the GIL when this module is loaded.
540592 */
593+ /* TODO: This is not quite true yet: there is a race in Xxo_setattro
594+ * for example.
595+ */
541596 {Py_mod_gil , Py_MOD_GIL_NOT_USED },
542597
543598
0 commit comments