Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C API] Add PyObject_VectorcallDict() to the limited C API #120881

Open
vstinner opened this issue Jun 22, 2024 · 1 comment
Open

[C API] Add PyObject_VectorcallDict() to the limited C API #120881

vstinner opened this issue Jun 22, 2024 · 1 comment

Comments

@vstinner
Copy link
Member

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.
@encukou
Copy link
Member

encukou commented Jun 25, 2024

I don't think this should be considered in isolation. I've opened a C API WG issue: capi-workgroup/api-evolution#48

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants