Make `_PyObject_CallFunctionVa` public

There’s no public API for using PyObject_CallFunction with a va_list. (Technically, you can get replicate the argument building by manually adding a () around a format string passed to Py_VaBuildValue, but that’s quite hacky.)

It would be nice if either the private _PyObject_CallFunctionVa was made public, or there was a public interface that wrapped it. This wouldn’t be anything complex, it would simply be:

PyObject *
PyObject_CallFunctionVa(PyObject *callable, const char *format, va_list vargs)
{
    PyObject *result;
    PyThreadState *tstate = _PyThreadState_GET();
    va_list va;

    va_copy(va, vargs);
    result = _PyObject_CallFunctionVa(tstate, callable, format, va);
    va_end(va);

    return result;
}

I would be happy to author the PR, if there are no objections.

In general, it would be good if each varargs function has a va_list alternative, and an array/tuple alternative (PyObject_Vectorcall or PyObject_Call in this case).
But, between this, #120881, and #86347, I think we should look at the call API as a whole and decide what it should be in general. I’ve opened a C API WG issue: Design the call API · Issue #48 · capi-workgroup/api-evolution · GitHub

3 Likes

(I’m not sure if I’m allowed to post on the CAPI WG repositories, so I’ll just put my thoughts here)

From the perspective of someone that’s not a core developer, I think the best improvement you guys could make here would be naming consistency. It would be useful if the argument style was denoted through the function name itself – e.g. PyObject_CallVector, PyObject_CallTuple, PyObject_CallVa.

With that being said, I’m personally not a fan of the “object + attribute” style – I try to avoid it when using the C API. (I’m not familiar with the internals of any of those functions, I’m assuming they have some sort of performance benefit, and that’s why people use them.)

Especially with vectorcall they’re quite efficient - the name gets looked up on the type (rather than the instance) and thus avoids creating an intermediate bound function, and self is just arg[0] in the vectorcall array. So it’s well worthwhile using the vectorcall ones. The others are mostly convenience.

1 Like

I’m not sure if I’m allowed to post on the CAPI WG repositories

You are! But posting here works too.

Maybe in this case we want to add a variant of Py_VaBuildValue (and Py_BuildValue) that always builds a tuple (without needing parentheses), and then direct people to the basic PyObject_Call.
In that future, PyObject_CallFunction and PyObject_CallMethod would only remain for backwards compatibility.

I like that approach - something like Py_BuildTuple and Py_VaBuildTuple? Although, I do know people who like PyObject_CallFunction specifically because it’s parameters are one less reference that they have to manage - but this is the C API anyway, reference management is inevitable, it’s just worth noting.

Or perhaps convenience? I use PyObject_CallMethod all the time, because it reduces the amount of reference counting and error handling I have to manage (and it’s great paired with ImportModule to import something and call a top-level function).

But I’m also okay with paying a little cost for that convenience. I’m not trying to fix my perf problems by calling back into Python :wink:

3 Likes