chris
chris

Reputation: 1718

Dynamic attribute in a Python C module

I have a custom Python module written in C, and I want to add an attribute to the module which is dynamically populated. E.g.:

import mymod
print(mymod.x)     # At this point, the value of x is computed

The name of the attribute is known in advance.

From what I understand, this should be possible using descriptors, but it is not working as expected. I implemented a custom type, implemented the tp_descr_get function for the type, and assigned an instance of the type to my module, but the tp_descr_get function is never called.

Here is my test module:

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stdio.h>

static struct PyModuleDef testmod = {
  PyModuleDef_HEAD_INIT,
  "testmod",
  NULL,
  -1
};

typedef struct testattrib_s {
  PyObject_HEAD
} testattrib;

static PyObject *testattrib_descr_get(PyObject *self, PyObject *obj, PyObject *type);
static int       testattrib_descr_set(PyObject *self, PyObject *obj, PyObject *value);

PyTypeObject testattribtype = {
  PyVarObject_HEAD_INIT (NULL, 0)
  "testattrib",                 /* tp_name */
  sizeof (testattrib),          /* tp_basicsize */
  /* lots of zeros omitted for brevity */
  testattrib_descr_get,         /* tp_descr_get */
  testattrib_descr_set          /* tp_descr_set */
};

PyMODINIT_FUNC
PyInit_testmod(void)
{
  if (PyType_Ready(&testattribtype)) {
    return NULL;
  }

  testattrib *attrib = PyObject_New(testattrib, &testattribtype);
  if (attrib == NULL) {
    return NULL;
  }

  PyObject *m = PyModule_Create(&testmod);
  if (m == NULL) {
    return NULL;
  }

  if (PyModule_AddObject(m, "myattrib", (PyObject *) attrib)) {
    return NULL;
  }

  return m;
}

static PyObject *testattrib_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
  printf("testattrib_descr_get called\n");
  Py_INCREF(self);
  return self;
}

static int testattrib_descr_set(PyObject *self, PyObject *obj, PyObject *value)
{
  printf("testattrib_descr_set called\n");
  return 0;
}

I test it like this:

import testmod

print(testmod.myattrib)   # should call tp_descr_get
testmod.myattrib = 1      # should call tp_descr_set

The getter/setter functions are never called. What am I doing wrong?

I am running Python 3.8.5 on macOS 12.0.1 with a build from Anaconda:

>>> sys.version
'3.8.5 (default, Sep  4 2020, 02:22:02) \n[Clang 10.0.0 ]'

Upvotes: 2

Views: 307

Answers (1)

Davis Herring
Davis Herring

Reputation: 39838

Descriptors operate only as attributes on a type. You would have to create your module as an instance of a module subclass equipped with the descriptor. The easiest way to do that is to use the Py_mod_create slot (not to be confused with __slots__).

Upvotes: 1

Related Questions