Description
From: #85283 (comment)
@AraHaan: Why do you need
PyObject_VectorcallDict()
? Can you elaborate?
In the limited ABI their lacks a version of vector call that accepts args
as a tuple, and kwargs
as a dict instead of the confusing to use kwnames
versions that I see no example usages of at all on the internet it seems.
I got my code to work well with PyVectorcall_Call
however, that is not in the limited ABI as far as I know in 3.12 at least.
Edit: **It is in the stable ABI and from looks of it now also in the limited ABI for 3.12 if one sets Py_LIMITED_API
to 0x030C00F0
(There really needs to be a document that documents all of the stable released version values of python to be made for the limited ABI tho).
Also PyObject_VectorcallDict
would be the only function that could accept a possibly null
args, and kwargs
for cases of my own wrapper calling functions in my own c extension:
PyObject *_PyObject_GetCallableMethod(PyObject *obj, PyObject *attr_name) {
PyObject *method = PyObject_GetAttr(obj, attr_name);
if (!PyCallable_Check(method)) {
Py_XDECREF(method);
return NULL;
}
return method;
}
PyObject *_PyObject_GetCallableMethodString(PyObject *obj, const char *name) {
PyObject *attr_name = PyUnicode_FromString(name);
PyObject *method = _PyObject_GetCallableMethod(obj, attr_name);
Py_XDECREF(attr_name);
return method;
}
int _PyObject_CallAndAwaitCoroutineAndKeywords(PyObject *awaitable, PyObject *obj, const char *name, PyObject *args, PyObject *kwargs, awaitcallback cb) {
PyObject *method = _PyObject_GetCallableMethodString(obj, name);
if (method == NULL || Py_IsNone(method)) {
PyErr_SetString(PyExc_RuntimeError, "'method' is NULL or 'None'.\n");
return -1;
}
// prevent problems where "PyObject_Call" would fail because args is NULL.
PyObject *args_copy = args;
if (args_copy == NULL) {
args_copy = PyTuple_New(0);
}
if (!PyTuple_Check(args_copy)) {
PyErr_SetString(PyExc_TypeError, "Provided args is not a tuple.\n");
Py_DECREF(args);
Py_DECREF(args_copy);
Py_DECREF(method);
return -1;
}
PyObject *coro = PyVectorcall_Call(method, args_copy, kwargs);
Py_DECREF(args);
Py_DECREF(args_copy);
Py_DECREF(method);
if (!coro) {
PyErr_Print();
return -1;
}
if (PyAwaitable_AddAwait(awaitable, coro, cb, NULL) < 0) {
return -1;
}
Py_DECREF(coro);
return 0;
}
int _PyObject_CallAndAwaitCoroutine(PyObject *awaitable, PyObject *obj, const char *name, PyObject *args, awaitcallback cb) {
return _PyObject_CallAndAwaitCoroutineAndKeywords(awaitable, obj, name, args, NULL, cb);
}
int _PyObject_CallAndAwaitCoroutineNoArgs(PyObject *awaitable, PyObject *obj, const char *name, awaitcallback cb) {
return _PyObject_CallAndAwaitCoroutine(awaitable, obj, name, PyTuple_New(0), cb);
}
In this code I use pyawaitable
to both define my own coroutines (awaitables) within c extension code, as well as being able call call say for example ones from asyncio
as well properly from C code without any RuntimeWarnings
getting emitted.
Benefits of having PyObject_VectorcallDict
in limited api:
PyObject_Vectorcall
is already in limited api. And allows passing in args without needing to pack the args into a tuple first.- it allows similar functionality, but similar to
PyVectorcall_Call
it allows passing in keyword args as a dict. - less chances of reference leaks or trying to decref an object that has 0 reference counts already. Reference issues are a true pain in the butt to resolve even when they can happen in the python core itself as well and the whole review process can easily miss a few places at review time.