2

I am writing a simple extension module using the C API. Here is a test example:

#include <Python.h>
#include <structmember.h>

typedef struct {
    PyObject_HEAD
    int i;
} MyObject;

static PyMemberDef my_members[] = {
    {"i", T_INT, offsetof(MyObject, i), READONLY, "Some integer"},
    {NULL}  /* Sentinel */
};


static int MyType_init(MyObject *self, PyObject *args, PyObject *kwds)
{
    char *keywords[] = {"i", NULL};
    int i = 0;

    if(!PyArg_ParseTupleAndKeywords(args, kwds, "|i", keywords, &i)) {
        return -1;
    }
    self->i = i;
    return 0;
}

static PyTypeObject MyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "_my.MyType",
    .tp_doc = "Pointless placeholder class",
    .tp_basicsize = sizeof(MyObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_init = (initproc)MyType_init,
    .tp_members = my_members,
};

static PyModuleDef my_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "_my",
    .m_doc = "My module is undocumented!",
    .m_size = -1,
};

/* Global entry point */

PyMODINIT_FUNC PyInit__my(void)
{
    PyObject *m = NULL;

    PyExc_MyException = PyErr_NewExceptionWithDoc("_my.MyException",
        "Indicates that something went horribly wrong.",
        NULL, NULL);
    if(PyExc_MyException == NULL) {
        return NULL;
    }

    if(PyType_Ready(&MyType) < 0) {
        goto err;
    }

    if((m = PyModule_Create(&my_module)) == NULL) {
        goto err;
    }

    Py_INCREF(&MyType);
    PyModule_AddObject(m, "MyType", (PyObject *)&MyType);
    PyModule_AddObject(m, "MyException", (PyObject *)PyExc_MyException);

    return m;

err:
    Py_CLEAR(PyExc_MyException);
    return NULL;
}

This module contains a class MyType and an exception MyException. The purpose of this module is to provide the base functionality for some additional code written in Python. I would like to be able to do something like:

my.py

from _my import *

class MyType(MyType):
    def __init__(i=0):
        raise MyException

Obviously this example is highly contrived. I just want to illustrate how I would like to use the names in my extension. Right now the import is working fine according to this rule:

If __all__ is not defined, the set of public names includes all names found in the module’s namespace which do not begin with an underscore character ('_').

However, I would like to have a finer level of control. So far, all I have been able to come up with is manually adding an __all__ attribute to the module:

    PyObject *all = NULL;
    ...
    all = Py_BuildValue("[s, s]", "MyType", "MyException");
    if(all == NULL) {
        goto err;
    }
    PyModule_AddObject(m, "__all__", all);
    ...
err:
    ...
    Py_CLEAR(all);

This seems a bit clunky. Is there a function in the C API for defining an internal equivalent to __all__ for a module?

Part of the reason that I think there may be an internal equivalent is that __all__ is after all a dunder attribute.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • 2
    there is no internal equivalent - __all__ is just an attribute in the module, with no magic whatsoever – nosklo Jun 13 '18 at 19:36
  • @nosklo. So the manual thing I have is the best I can do? I figured that as a dunder attribute it would have some special translation into the underlying implementation :( – Mad Physicist Jun 13 '18 at 19:42
  • even in pure python, the only thing you can you do is `__all__ = ['foo', 'bar', 'baz']` – nosklo Jun 13 '18 at 19:43
  • @nosklo. Sure. But you also just do `def __getitem__(...`, but that's a very special slot. – Mad Physicist Jun 13 '18 at 19:44
  • 3
    most c modules I have seen, have a helper `.py` file, so they have a `_foo.c` and a `foo.py` that imports things from `_foo`. `__all__` would be defined in foo.py then, maybe that's what you want to do? – nosklo Jun 13 '18 at 19:46
  • @nosklo. Exactly. If you post the details in an answer, I would be happy to accept. By the way, is the way I am extending the class `MyType` acceptable? – Mad Physicist Jun 13 '18 at 19:47
  • 1
    In a C module all the implementation functions are going to be C and not exported regardless, so I'm not certain I see the point of an `__all__` attribute. – Ignacio Vazquez-Abrams Jun 15 '18 at 22:10
  • @IgnacioVazquez-Abrams. The attribute controls what comes in with a star import. It's sometimes useful to import only a part of the public API that way. Nothing to do with C functions at all really. – Mad Physicist Jun 15 '18 at 22:52
  • I know what it does, but it would only be important if you make the implementation functions both importable and callable which would require more work than just pretending that they don't exist at the Python level altogether. – Ignacio Vazquez-Abrams Jun 15 '18 at 23:00
  • @IgnacioVazquez-Abrams. That would be equivalent to prepending `_` to everything in your Python module in lieu of defining `__all__`. Generally adequate, but sometimes just not what you want. – Mad Physicist Jun 16 '18 at 01:06

0 Answers0