-
Notifications
You must be signed in to change notification settings - Fork 1
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
Design the call API #48
Comments
Given that this hasn't been touched for a little while, I thought it might be useful to share some of my ideas to get some discussion stirring up! AFAICS, the concern here is that a). the call API isn't consistent and b). we don't want to deal with addressing each individual function over time. I'll try to address both those problems here. I think it should be, more or less, clear from the function name on what you should pass to it (I mentioned this here, for reference.) As of now, many of the call functions aren't clear, or worse, misleading! For example, at a glance, seeing static PyObject *
my_func(PyObject *self, PyObject *python_function)
{
PyObject *res = PyObject_CallFunction(python_function) // ???
// ...
} So, we should be more clear in the name. I originally suggested putting what should be passed in the name itself. We're already sort-of doing this, just not consistently. For example, Now, the equivalent of static PyObject *
my_func(PyObject *self, PyObject *whatever)
{
// No dict parameter -- more on that later
PyObject *res = PyCall_Vector(whatever, (PyObject *[]) { Py_None }, 1);
} Though,
Now, isn't that prettier! static PyObject *
my_func(PyObject *self, PyObject *whatever)
{
PyObject *foo = PyCall_Format(whatever, "i", 42);
if (foo == NULL)
{
return NULL;
}
PyObject *bar = PyCall_MethodOneArg(foo, );
} To address the consistency issue, we should have an equivalent API for each subcategory of the calling API. For example, a format string variation of As of now, I think we need the following subcategories:
We would need some of these for each other as well, e.g. Unfortunately, this does create a lot of functions, but it does get everything people could need in one sweep, without having to take feature requests in the future. Maybe this would have been better for |
I like I hope that if we get a low-overhead way of borrowing an array of Similarly, for Maybe we should add a more powerful GetAttr variant that can retreive an unbound method (and a flag that it did so), which could then be passed more directly to the actual call function? |
I like that approach. Disclaimer: I'm not all that familiar with the tuple implementation -- I'm guessing that For converting variadic arguments into an array, we could possibly do this with some macro shenanigans. I think something like this could work? #define _Py_NUMARGS(...) (sizeof((PyObject*[]) { __VA_ARGS__ }) / sizeof(PyObject*))
#define PyCall_Variadic(obj, ...) PyObject_Vectorcall(obj, (PyObject*[]) { __VA_ARGS__ }, _Py_NUMARGS(__VA_ARGS__), NULL) |
Not for the limited API.
Yup. IMO, it's fine to design variadic functions for C/C++ only, if we also have array+size variants of the functions. |
We could add a stable Though, this does raise a second question: maybe using an array and size was a mistake. Should we add a variation of vectorcall that uses a |
Who would hold the references to the objects? AFAICS, Alternatively, there could be a
For my own personal opinion: No. Array+size forever. Zero-terminated strings are a trap C fell into in 1970s to save a byte. They should only be used when necessary, or for hand-written literals (since C makes defining an array+size frustratingly un-ergonomic), and always as an alternative to array+size. Rant aside,
So accepting NULL-terminated array would mean extra CPU time in most cases. When that's no the case, the caller can do the counting on their side. |
I was under the impression that it would contain borrowed references, but I realize now that could cause a bit of a thread safety issue... I see where the design problem comes in, now! Incidentally, we could also take #123372 into account when designing this. New functions should probably only use vectorcall if it would speed things up, so likewise, we should have an API for quickly constructing a tuple from an array. |
After some pondering, I'm guessing that we still want two main variations for calling: vectorcall, and a tuple. Designing new APIs to borrow a tuple's items so we can macro them into vectorcall just doesn't seem worth it :( -- if the user has a tuple, they'll likely just use |
The object calling API has grown organically over the years, and looks like an incomplete Cartesian product of features, with not-always-consistent naming.
For reference, here are the kinds of arguments the
Call
functions take:Callable:
char*
arg[0]
attribute; name given as Python stringPositional arguments:
...
)va_list
Keyword arguments:
We have some requests to change things, like:
Rather than evaluate each of those individually, perhaps we should look at the big picture.
How should this look, ideally?
How do we get there?
The text was updated successfully, but these errors were encountered: