0

Let's say I want to write a module called countries that contains information about the countries of the world. I want to write it in C. It will expose a member called countries which is a list of objects of type Country. The Country type has two members: name and capital, both strings. So we could do something like this:

>>> from countries import countries >>> countries[0].name Afghanistan >>> countries[0].capital Kabul 

Following the various scraps of tutorials I was able to find online, I figured out how to create my custom Country type in C. It looks basically like this:

typedef struct { PyObject_HEAD char *name; char *capital; } Country; static struct PyMemberDef CountryMembers[] = { {"name", T_OBJECT_EX, offsetof(Country, name), 0, "name"}, {"capital", T_OBJECT_EX, offsetof(Country, capital), 0, "capital"}, {NULL} }; static PyTypeObject CountryType = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "countries.Country", .tp_doc = "Country", .tp_basicsize = sizeof(Country), .tp_itemsize = 0, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_new = PyType_GenericNew, .tp_members = CountryMembers }; 

and then in the module init function, call Py_TypeReady and add the type to the module. However, what I cannot figure out how to do is create a new instance of this type, in C, add it to the module, and populate its fields. The closest I can get is this, again in the module init function:

PyObject *afghanistan = PyObject_CallObject((PyObject*) &CountryType, NULL); PyModule_AddObject(local_module, "afghanistan", (PyObject*) afghanistan); 

But I don't know how to fill in the members.

3
  • T_OBJECT_EX and char * don't match. Either T_OBJECT_EX should be T_STRING, or char * should be PyObject *, depending on how you actually want these objects to work. Commented Dec 3, 2021 at 9:29
  • You really ought to write a meaningful constructor, too. Commented Dec 3, 2021 at 9:32
  • @user2357112supportsMonica Do I have to? That feels like it should be unnecessary, considering I just want to populate these things with fixed values. I don't intend for anyone to construct objects of type Country from Python. Commented Dec 3, 2021 at 10:21

1 Answer 1

1

You just need to cast the PyObject* to a Country* and then you can set the members yourself:

Country *afghanistan = (Country*)PyObject_CallObject((PyObject*) &CountryType, NULL); afghanistan->name = "Afghanistan"; afghanistan->captial = "Kabul"; PyModule_AddObject(local_module, "afghanistan", (PyObject*) afghanistan); 

Obviously if you want name and capital to point to non-static strings then you need to malloc them and free them appropriately in the constructor.


Given that it sounds like you don't want this class to be constructable from Python then you might to properly ban that with Py_TPFLAGS_DISALLOW_INSTANTIATION (on older versions of Python just set tp_new to NULL). You'd then create a C-only "constructor" function:

static Country* MakeCountry(const char* name, const char* capital) { Country *c = CountryType.tp_alloc(&CountryType, 0); c->name = name; c->capital = capital; return c; } 

And use that function instead of PyObject_CallObject((PyObject*)&CountryType, NULL).

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.