What was/is that "void* <unused>" argument for, that some __sizeof__ methods have in CPython?

Viewed 60

Several C types in the CPython source have a __sizeof__ method so they can present approximately accurate sizes (in bytes) for instances with sys.getsizeof.

These methods are declared METH_NOARG but some do have a void* whatever argument, for example itertools.product.__sizeof__:

static PyObject *
product_sizeof(productobject *lz, void *unused)
{
    Py_ssize_t res;

    res = _PyObject_SIZE(Py_TYPE(lz));
    res += PyTuple_GET_SIZE(lz->pools) * sizeof(Py_ssize_t);
    return PyLong_FromSsize_t(res);
}

static PyMethodDef product_methods[] = {
    /* ... */
    {"__sizeof__",      (PyCFunction)product_sizeof,      METH_NOARGS,  sizeof_doc},
    {NULL,              NULL}   /* sentinel */
};

Some have it (e.g. 1, 2), while others don't (for example: 1, 2). It doesn't seem to make sense to have an argument when you declare it a method without arguments.

Given the name "unused" it seems like it probably had some meaning once but I can't figure out for what. I've tried using "git blame" and reading through some of the related issues but couldn't find anything with respect to this "unused" argument. I also thought it may be related to the "default" argument for sys.getsizeof but that's not passed through to the method - and what would be the point for the method to know what default was given...

I'm interested it: What was the purpose of the argument (and when it became obsolete why wasn't it removed).

2 Answers

Based on the comments of Martijn Pieters I was able to find out why the methods have a second argument. Those listed without it had a wrapper function inside the argument clinic files (so it was just "hidden"). And it's not only relevant for __sizeof__, but to all METH_NOARG methods.

The METH_NOARG documentation is:

METH_NOARGS

Methods without parameters don’t need to check whether arguments are given if they are listed with the METH_NOARGS flag. They need to be of type PyCFunction. The first parameter is typically named self and will hold a reference to the module or object instance. In all cases the second parameter will be NULL.

However PyCFunctions have two arguments:

PyCFunction

Type of the functions used to implement most Python callables in C. Functions of this type take two PyObject* parameters and return one such value. If the return value is NULL, an exception shall have been set. If not NULL, the return value is interpreted as the return value of the function as exposed in Python. The function must return a new reference.

(Emphasis mine)

The corresponding typedef can be found in methodobject:

typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);

So all methods declared inside a PyMethodDef require (at least) two arguments, even if they don't use the second one. That's why it's called "unused", it had never any meaning for the method itself.

There's no specific type of function that only takes a single parameter. PyCFunctions always take two as its documentation states:

Type of the functions used to implement most Python callables in C. Functions of this type take two PyObject* parameters and return one such value.

The METH_NOARGS case doesn't mean that the function will only have a single parameter, rather, it means that the second parameter will always be NULL:

The first parameter is typically named self and will hold a reference to the module or object instance. In all cases the second parameter will be NULL.

you can also see this directly in call.c:_PyMethodDef_RawFastCallKeywords where the call is made:

case METH_NOARGS:
    // After snipping checks away
    result = (*meth) (self, NULL);

There's a number of discussions covering this, see here, here and here for some of these.


As for the versions that only have a single argument, as Martijn points out, these use argument clinic to hide that.

Related