How to set docstrings for extensions supporting PyNumberMethods?

Ziga Seilnacht ziga.seilnacht at gmail.com
Sun Mar 4 03:14:23 EST 2007


Nick Alexander wrote:
> Hello,
>
> I am writing a python extension (compiled C code) that defines an
> extension type with PyNumberMethods.  Everything works swimmingly,
> except I can't deduce a clean way to set the docstring for tp_*
> methods.  That is, I always have
>
> type.__long__.__doc__ == 'x.__long__() <==> long(x)'
>
> which a quick glance at the Python 2.5 source shows is the default.
>
> I have found that I can use PyObject_GetAttr and PyWrapperDescrObject
> and set the descriptor objects d_base->doc to a char pointer... but I
> can't tell if this is safe.  Or the right way to do it.
>
> If I'm on the wrong list, please let me know!
> Thanks,
> Nick Alexander

I think that the right way is to add the methods to the tp_methods
slot and use METH_COEXIST in the PyMethodDef flags field. Example:

/* start of silly module */
#include "Python.h"

typedef struct {
    PyObject_HEAD
    double value;
} SillyNumber_Object;

/* Forward declarations */
static PyTypeObject SillyNumber_Type;

#define SillyNumber_Check(op) PyObject_TypeCheck(op,
&SillyNumber_Type)

static PyObject *
new_SillyNumber(PyTypeObject *type, double value)
{
    PyObject *self;

    self = type->tp_alloc(type, 0);
    if (self == NULL)
        return NULL;

    ((SillyNumber_Object *)self)->value = value;
    return self;
}

static PyObject *
SillyNumber_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    double value = 0.0;
    static char *kwlist[] = {"value", 0};

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|d:SillyNumber",
                                     kwlist, &value))
        return NULL;

    return new_SillyNumber(type, value);
}

static PyObject *
SillyNumber_add(PyObject *left, PyObject *right)
{
    double sum;

    if (!SillyNumber_Check(left) || !SillyNumber_Check(right)) {
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }

    sum = (((SillyNumber_Object *)left)->value +
           ((SillyNumber_Object *)right)->value);

    return new_SillyNumber(&SillyNumber_Type, sum);
}

static PyObject *
SillyNumber_radd(PyObject *right, PyObject *left)
{
    return SillyNumber_add(left, right);
}

static PyNumberMethods SillyNumber_as_number = {
    SillyNumber_add,    /* nb_add */
    0,                  /* nb_subtract */
    0,                  /* nb_multiply */
    0,                  /* nb_divide */
    0,                  /* nb_remainder */
    0,                  /* nb_divmod */
    0,                  /* nb_power */
    0,                  /* nb_negative */
    0,                  /* nb_positive */
    0,                  /* nb_absolute */
    0,                  /* nb_nonzero */
};

static PyMethodDef SillyNumber_methods[] = {
    {"__add__", SillyNumber_add, METH_O | METH_COEXIST,
        "Add two SillyNumbers."},
    {"__radd__", SillyNumber_radd, METH_O | METH_COEXIST,
        "Same as __add__."},
    {NULL, NULL, 0, NULL}
};

static PyTypeObject SillyNumber_Type = {
    PyObject_HEAD_INIT(NULL)
    0,                          /* ob_size */
    "silly.SillyNumber",        /* tp_name */
    sizeof(SillyNumber_Object), /* tp_basicsize */
    0,                          /* tp_itemsize */
    0,                          /* tp_dealloc */
    0,                          /* tp_print */
    0,                          /* tp_getattr */
    0,                          /* tp_setattr */
    0,                          /* tp_compare */
    0,                          /* tp_repr */
    &SillyNumber_as_number,     /* 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 */
    Py_TPFLAGS_CHECKTYPES |     /* PyNumberMethods do their own
coercion */
    Py_TPFLAGS_BASETYPE,        /* SillyNumber_Type allows subclassing
*/
    "Silly float numbers",      /* tp_doc */
    0,                          /* tp_traverse */
    0,                          /* tp_clear */
    0,                          /* tp_richcompare */
    0,                          /* tp_weaklistoffset */
    0,                          /* tp_iter */
    0,                          /* tp_iternext */
    SillyNumber_methods,        /* tp_methods */
    0,                          /* tp_members */
    0,                          /* tp_getset */
    0,                          /* tp_base */
    0,                          /* tp_dict */
    0,                          /* tp_descr_get */
    0,                          /* tp_descr_set */
    0,                          /* tp_dictoffset */
    0,                          /* tp_init */
    0,                          /* tp_alloc */
    SillyNumber_new,            /* tp_new */
    0,                          /* tp_free */
};

static PyMethodDef silly_methods[] = {
    {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC
initsilly(void)
{
    PyObject *module;

    module = Py_InitModule3("silly", silly_methods,
                            "silly floating number type");
    if (module == NULL)
        return;

    if (PyType_Ready(&SillyNumber_Type) < 0)
        return;

    Py_INCREF(&SillyNumber_Type);
    PyModule_AddObject(module, "SillyNumber", (PyObject *)
&SillyNumber_Type);
}

/* end of silly module */


Results from the interactive session:

>>> import silly
>>> print silly.SillyNumber.__add__.__doc__
Add two SillyNumbers.
>>> print silly.SillyNumber.__radd__.__doc__
Same as __add__.
>>> n1 = silly.SillyNumber(3)
>>> n2 = silly.SillyNumber(3.14)
>>> type(n1 + n2) is silly.SillyNumber
True

HTH,
Ziga




More information about the Python-list mailing list