So I'm learning how to make Python modules in C and today I wanted to learn how to create a custom type.
I wrote a module with one class named "Car" which stores a name
, year
and maxGas
.
Problem is, when I try to create an instance of this class in Python the name
property contains random characters instead of the given name.
#include <Python.h>
#include <stdio.h> //printf debugging
#include <stdlib.h> //free
#include "structmember.h"
typedef struct {
PyObject_HEAD
char* name; //allocated and freed by Python
int year;
float maxGas;
} cars_Car;
static int Car_init(cars_Car* self, PyObject* args, PyObject* kwargs){
/*
//Method #1
char* tmp;
// v- Note the &
if(!PyArg_ParseTuple(args, "sif", &tmp, &self->year, &self->maxGas)){
return -1;
}
self->name = (char*)malloc( sizeof(char) * (strlen(tmp) + 1) );
sprintf(self->name, "%s", tmp);
printf("Given name: %s\n", self->name);
//Works
*/
//Method #2
// v- Note the &
if(!PyArg_ParseTuple(args, "sif", &self->name, &self->year, &self->maxGas)){
return -1;
}
printf("Given name: %s\n", self->name); // Works, but repr() and name/getName() are broken
return 0;
}
static void Car_dealloc(cars_Car* self){
//free(self->name); //For Method #2 in init()
Py_TYPE(self)->tp_free((PyObject*)self);
}
static PyObject* Car_repr(cars_Car* self){
return PyUnicode_FromFormat("<Car name=\"%s\" year=%d tankSize=%SL>",
self->name, self->year, Py_BuildValue("f", self->maxGas) /* %f not supported, so we convert it to a Python string */ );
}
static PyObject* Car_new(PyTypeObject* type, PyObject* args, PyObject* kwargs){
cars_Car* self;
self = (cars_Car*)type->tp_alloc(type, 0);
self->name = NULL;
self->year = 0;
self->maxGas = 0.0f;
return (PyObject*)self;
}
/* Methods */
static PyObject* Car_getName(cars_Car* self, PyObject* args){
return Py_BuildValue("s", self->name);
}
static PyObject* Car_getYear(cars_Car* self, PyObject* args){
return Py_BuildValue("i", self->year);
}
static PyObject* Car_getTankSize(cars_Car* self, PyObject* args){
return Py_BuildValue("f", self->maxGas);
}
static PyMethodDef Car_methods[] = {
{"getName", (PyCFunction)Car_getName, METH_NOARGS,
"Get the car's name.",
},
{"getYear", (PyCFunction)Car_getYear, METH_NOARGS,
"Get the car's year.",
},
{"getTankSize", (PyCFunction)Car_getTankSize, METH_NOARGS,
"Get the car's tank size.",
},
{NULL}
};
static struct PyMemberDef Car_members[] = {
{"name", T_STRING, offsetof(cars_Car, name), READONLY, "Car name"},
{"year", T_INT, offsetof(cars_Car, year), READONLY, "Car year"},
{"maxGas", T_FLOAT, offsetof(cars_Car, maxGas), READONLY, "Car maxGas"},
{NULL}
};
static PyTypeObject cars_CarType = {
PyVarObject_HEAD_INIT(NULL, 0)
"cars.Car", /* tp_name */
sizeof(cars_Car), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)Car_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)Car_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Cars objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Car_methods, /* tp_methods */
Car_members, /* tp_members */
NULL, /* tp_getset */
NULL, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Car_init, /* tp_init */
0, /* tp_alloc */
Car_new, /* tp_new */
0, /* tp_free */
0 /* tp_is_gc */
};
static PyModuleDef modcars = {
PyModuleDef_HEAD_INIT,
"cars",
"Example module that creates an extension type.",
-1,
NULL, NULL, NULL, NULL, NULL
};
PyMODINIT_FUNC PyInit_Cars(void) {
PyObject* m;
cars_CarType.tp_new = PyType_GenericNew;
if (PyType_Ready(&cars_CarType) < 0)
return NULL;
m = PyModule_Create(&modcars);
if (m == NULL)
return NULL;
Py_INCREF(&cars_CarType);
PyModule_AddObject(m, "Car", (PyObject *)&cars_CarType);
return m;
}
As you can see, I mentioned 2 different methods in the Car_init
function.
When using the first method, I get the following result:
>>> import Cars
>>> x = Cars.Car("The Big Car", 2020, 4.2)
Given name: The Big Car #printf() works
>>> x
<Car name="????" year=2020 tankSize=4.199999809265137L #??? - bunch of random characters
I'm not sure what's causing this, although my guess is that my code is probably writing to some memory address where it's not supposed to. It also looks like there's a floating point precision error.
Interestingly when I directly call getName()
or try to access the name
attribute, it works.
>>> Cars.Car("The Big Car", 2020, 4.2).name
'The Big Car'
>>> Cars.Car("The Big Car", 2020, 4.2).getName()
'The Big Car'
>>> repr(Cars.Car("The Big Car", 2020, 4.2))
'<Car name="The Big Car" year=2020 tankSize=4.199999809265137L>'
If I switch to the second method everything works fine. I'm not sure why using a temporary variable helps. Also my code doesn't write anything to name
, except when initialising it. Everywhere else it's just being read. I also thought about the garbage collector, maybe it's freeing name
?