1
2/* Use this file as a template to start implementing a module that
3 also declares object types. All occurrences of 'Xxo' should be changed
4 to something reasonable for your objects. After that, all other
5 occurrences of 'xx' should be changed to something reasonable for your
6 module. If your module is named foo your source file should be named
7 foo.c or foomodule.c.
8
9 You will probably want to delete all references to 'x_attr' and add
10 your own types of attributes instead. Maybe you want to name your
11 local variables other than 'self'. If your object type is needed in
12 other files, you'll have to create a file "foobarobject.h"; see
13 floatobject.h for an example.
14
15 This module roughly corresponds to::
16
17 class Xxo:
18 """A class that explicitly stores attributes in an internal dict"""
19
20 def __init__(self):
21 # In the C class, "_x_attr" is not accessible from Python code
22 self._x_attr = {}
23
24 def __getattr__(self, name):
25 return self._x_attr[name]
26
27 def __setattr__(self, name, value):
28 self._x_attr[name] = value
29
30 def __delattr__(self, name):
31 del self._x_attr[name]
32
33 def demo(o, /):
34 if isinstance(o, str):
35 return o
36 elif isinstance(o, Xxo):
37 return o
38 else:
39 raise Error('argument must be str or Xxo')
40
41 class Error(Exception):
42 """Exception raised by the xxlimited module"""
43
44 def foo(i: int, j: int, /):
45 """Return the sum of i and j."""
46 # Unlike this pseudocode, the C function will *only* work with
47 # integers and perform C long int arithmetic
48 return i + j
49
50 def new():
51 return Xxo()
52
53 def Str(str):
54 # A trivial subclass of a built-in type
55 pass
56 */
57
58#define Py_LIMITED_API 0x030a0000
59
60#include "Python.h"
61
62// Module state
63typedef struct {
64 PyObject *Xxo_Type; // Xxo class
65 PyObject *Error_Type; // Error class
66} xx_state;
67
68
69/* Xxo objects */
70
71// Instance state
72typedef struct {
73 PyObject_HEAD
74 PyObject *x_attr; /* Attributes dictionary */
75} XxoObject;
76
77// XXX: no good way to do this yet
78// #define XxoObject_Check(v) Py_IS_TYPE(v, Xxo_Type)
79
80static XxoObject *
81newXxoObject(PyObject *module)
82{
83 xx_state *state = PyModule_GetState(module);
84 if (state == NULL) {
85 return NULL;
86 }
87 XxoObject *self;
88 self = PyObject_GC_New(XxoObject, (PyTypeObject*)state->Xxo_Type);
89 if (self == NULL) {
90 return NULL;
91 }
92 self->x_attr = NULL;
93 return self;
94}
95
96/* Xxo finalization */
97
98static int
99Xxo_traverse(XxoObject *self, visitproc visit, void *arg)
100{
101 // Visit the type
102 Py_VISIT(Py_TYPE(self));
103
104 // Visit the attribute dict
105 Py_VISIT(self->x_attr);
106 return 0;
107}
108
109static int
110Xxo_clear(XxoObject *self)
111{
112 Py_CLEAR(self->x_attr);
113 return 0;
114}
115
116static void
117Xxo_finalize(XxoObject *self)
118{
119 Py_CLEAR(self->x_attr);
120}
121
122static void
123Xxo_dealloc(XxoObject *self)
124{
125 Xxo_finalize(self);
126 PyTypeObject *tp = Py_TYPE(self);
127 freefunc free = PyType_GetSlot(tp, Py_tp_free);
128 free(self);
129 Py_DECREF(tp);
130}
131
132
133/* Xxo attribute handling */
134
135static PyObject *
136Xxo_getattro(XxoObject *self, PyObject *name)
137{
138 if (self->x_attr != NULL) {
139 PyObject *v = PyDict_GetItemWithError(self->x_attr, name);
140 if (v != NULL) {
141 Py_INCREF(v);
142 return v;
143 }
144 else if (PyErr_Occurred()) {
145 return NULL;
146 }
147 }
148 return PyObject_GenericGetAttr((PyObject *)self, name);
149}
150
151static int
152Xxo_setattro(XxoObject *self, PyObject *name, PyObject *v)
153{
154 if (self->x_attr == NULL) {
155 // prepare the attribute dict
156 self->x_attr = PyDict_New();
157 if (self->x_attr == NULL) {
158 return -1;
159 }
160 }
161 if (v == NULL) {
162 // delete an attribute
163 int rv = PyDict_DelItem(self->x_attr, name);
164 if (rv < 0 && PyErr_ExceptionMatches(PyExc_KeyError)) {
165 PyErr_SetString(PyExc_AttributeError,
166 "delete non-existing Xxo attribute");
167 return -1;
168 }
169 return rv;
170 }
171 else {
172 // set an attribute
173 return PyDict_SetItem(self->x_attr, name, v);
174 }
175}
176
177/* Xxo methods */
178
179static PyObject *
180Xxo_demo(XxoObject *self, PyTypeObject *defining_class,
181 PyObject **args, Py_ssize_t nargs, PyObject *kwnames)
182{
183 if (kwnames != NULL && PyObject_Length(kwnames)) {
184 PyErr_SetString(PyExc_TypeError, "demo() takes no keyword arguments");
185 return NULL;
186 }
187 if (nargs != 1) {
188 PyErr_SetString(PyExc_TypeError, "demo() takes exactly 1 argument");
189 return NULL;
190 }
191
192 PyObject *o = args[0];
193
194 /* Test if the argument is "str" */
195 if (PyUnicode_Check(o)) {
196 Py_INCREF(o);
197 return o;
198 }
199
200 /* test if the argument is of the Xxo class */
201 if (PyObject_TypeCheck(o, defining_class)) {
202 Py_INCREF(o);
203 return o;
204 }
205
206 Py_INCREF(Py_None);
207 return Py_None;
208}
209
210static PyMethodDef Xxo_methods[] = {
211 {"demo", (PyCFunction)(void(*)(void))Xxo_demo,
212 METH_METHOD | METH_FASTCALL | METH_KEYWORDS, PyDoc_STR("demo(o) -> o")},
213 {NULL, NULL} /* sentinel */
214};
215
216/* Xxo type definition */
217
218PyDoc_STRVAR(Xxo_doc,
219 "A class that explicitly stores attributes in an internal dict");
220
221static PyType_Slot Xxo_Type_slots[] = {
222 {Py_tp_doc, (char *)Xxo_doc},
223 {Py_tp_traverse, Xxo_traverse},
224 {Py_tp_clear, Xxo_clear},
225 {Py_tp_finalize, Xxo_finalize},
226 {Py_tp_dealloc, Xxo_dealloc},
227 {Py_tp_getattro, Xxo_getattro},
228 {Py_tp_setattro, Xxo_setattro},
229 {Py_tp_methods, Xxo_methods},
230 {0, 0}, /* sentinel */
231};
232
233static PyType_Spec Xxo_Type_spec = {
234 .name = "xxlimited.Xxo",
235 .basicsize = sizeof(XxoObject),
236 .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
237 .slots = Xxo_Type_slots,
238};
239
240
241/* Str type definition*/
242
243static PyType_Slot Str_Type_slots[] = {
244 {0, 0}, /* sentinel */
245};
246
247static PyType_Spec Str_Type_spec = {
248 .name = "xxlimited.Str",
249 .basicsize = 0,
250 .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
251 .slots = Str_Type_slots,
252};
253
254
255/* Function of two integers returning integer (with C "long int" arithmetic) */
256
257PyDoc_STRVAR(xx_foo_doc,
258"foo(i,j)\n\
259\n\
260Return the sum of i and j.");
261
262static PyObject *
263xx_foo(PyObject *module, PyObject *args)
264{
265 long i, j;
266 long res;
267 if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
268 return NULL;
269 res = i+j; /* XXX Do something here */
270 return PyLong_FromLong(res);
271}
272
273
274/* Function of no arguments returning new Xxo object */
275
276static PyObject *
277xx_new(PyObject *module, PyObject *Py_UNUSED(unused))
278{
279 XxoObject *rv;
280
281 rv = newXxoObject(module);
282 if (rv == NULL)
283 return NULL;
284 return (PyObject *)rv;
285}
286
287
288
289/* List of functions defined in the module */
290
291static PyMethodDef xx_methods[] = {
292 {"foo", xx_foo, METH_VARARGS,
293 xx_foo_doc},
294 {"new", xx_new, METH_NOARGS,
295 PyDoc_STR("new() -> new Xx object")},
296 {NULL, NULL} /* sentinel */
297};
298
299
300/* The module itself */
301
302PyDoc_STRVAR(module_doc,
303"This is a template module just for instruction.");
304
305static int
306xx_modexec(PyObject *m)
307{
308 xx_state *state = PyModule_GetState(m);
309
310 state->Error_Type = PyErr_NewException("xxlimited.Error", NULL, NULL);
311 if (state->Error_Type == NULL) {
312 return -1;
313 }
314 if (PyModule_AddType(m, (PyTypeObject*)state->Error_Type) < 0) {
315 return -1;
316 }
317
318 state->Xxo_Type = PyType_FromModuleAndSpec(m, &Xxo_Type_spec, NULL);
319 if (state->Xxo_Type == NULL) {
320 return -1;
321 }
322 if (PyModule_AddType(m, (PyTypeObject*)state->Xxo_Type) < 0) {
323 return -1;
324 }
325
326 // Add the Str type. It is not needed from C code, so it is only
327 // added to the module dict.
328 // It does not inherit from "object" (PyObject_Type), but from "str"
329 // (PyUnincode_Type).
330 PyObject *Str_Type = PyType_FromModuleAndSpec(
331 m, &Str_Type_spec, (PyObject *)&PyUnicode_Type);
332 if (Str_Type == NULL) {
333 return -1;
334 }
335 if (PyModule_AddType(m, (PyTypeObject*)Str_Type) < 0) {
336 return -1;
337 }
338 Py_DECREF(Str_Type);
339
340 return 0;
341}
342
343static PyModuleDef_Slot xx_slots[] = {
344 {Py_mod_exec, xx_modexec},
345 {0, NULL}
346};
347
348static int
349xx_traverse(PyObject *module, visitproc visit, void *arg)
350{
351 xx_state *state = PyModule_GetState(module);
352 Py_VISIT(state->Xxo_Type);
353 Py_VISIT(state->Error_Type);
354 return 0;
355}
356
357static int
358xx_clear(PyObject *module)
359{
360 xx_state *state = PyModule_GetState(module);
361 Py_CLEAR(state->Xxo_Type);
362 Py_CLEAR(state->Error_Type);
363 return 0;
364}
365
366static struct PyModuleDef xxmodule = {
367 PyModuleDef_HEAD_INIT,
368 .m_name = "xxlimited",
369 .m_doc = module_doc,
370 .m_size = sizeof(xx_state),
371 .m_methods = xx_methods,
372 .m_slots = xx_slots,
373 .m_traverse = xx_traverse,
374 .m_clear = xx_clear,
375 /* m_free is not necessary here: xx_clear clears all references,
376 * and the module state is deallocated along with the module.
377 */
378};
379
380
381/* Export function for the module (*must* be called PyInit_xx) */
382
383PyMODINIT_FUNC
384PyInit_xxlimited(void)
385{
386 return PyModuleDef_Init(&xxmodule);
387}
388