-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 590: update #1028
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
PEP 590: update #1028
Conversation
What's the reason for I'm not convinced about the limitation that IMO IMO |
But Python 3 doesn't really have "unbound methods". It has functions. |
It's a simplification and optimization. Imagine a C function which doesn't take keywords. With the current PEP 590, it needs to check for that using
I suggest to simplify that to
|
There is no good reason. I just wanted to have a simpler way to write
Second, it's also more future-proof if we decide to remove the flag |
Right, but there are other C API functions referring to a protocol like |
NULL vectrorcall
Unfortunately, that makes the type optimization impossible -- we want METHOD_DESCRIPTOR/FUNCTION_DESCRIPTORBut Python 3 does have unbound methods:
"method" describes the mechanism better than "function". limitation that kwnames can't be the empty tupleYou make a fair point. But let's enforce this, at least in debug mode. PyVectorcall_CheckSince I'm currently at PyCon sprints, I asked Victor for another point of view here. He agrees that it's not necessary. PyVectorcall_Call / PyCall_MakeVectorCallLet's keep |
Is there a problem with using a generic vectorcall wrapper for all types? This could be set up by |
pep-0590.rst
Outdated
* ``PyObject **args``: A vector of arguments | ||
* ``PyTupleObject *kwnames``: A tuple of the names of the named arguments. | ||
* ``PyObject *kwnames``: Either ``NULL`` or a non-empty tuple of the names of the keyword arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the non-empty
part
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? The protocol is easier to use if there is a unique way to specify "no keyword arguments".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have a strong opinion here, but I agree with Jeroen. The caller probably knows already if the tuple is empty or not, so why not help the callee here? As long as PyObject_VectorCall()
is used for calling into vectorcallees, it can normalise that case internally, and everyone who does not use it is on their own anyway.
pep-0590.rst
Outdated
|
||
This is implemented by the function pointer type: | ||
``typedef PyObject *(*vectorcall)(PyObject *callable, Py_ssize_t n, PyObject** args, PyTupleObject *kwnames);`` | ||
``typedef PyObject *(*vectorcallfunc)(PyObject *callable, Py_ssize_t n, PyObject **args, PyObject *kwnames);`` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given it is a function pointer, the func
seems redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just clearer to use a name ending in func
. CPython has many such names for function pointer types ending in func
, for example
typedef PyObject *(*wrapperfunc)(PyObject *self, PyObject *args,
void *wrapped);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disallowing an empty tuple adds a non-obvious edge case.
What's wrong with non-obvious edge cases? They rely on either everyone implementing the protocol reading the docs and noticing this case, or on checking for the case and enforcing it. In the ideal world, that would be enough. But if we have a choice, and it doesn't hurt performance, let's not add one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disallowing an empty tuple adds a non-obvious edge case.
I argue the oppsite: allowing an empty tuple adds a non-obvious edge case.
We should really view this from the side of the callee (the C function), as that's what external projects will implement. From that viewpoint, having two ways to indicate "no keywords" is a non-obvious edge case.
The side of the caller (which should ensure not to send an empty tuple) is CPython and there the issue of people implementing the protocol wrongly doesn't arise. External C code is not expected to manually use tp_vectorcall_offset
to make vectorcalls: it is expected to use an API like PyCall_Vectorcall()
and that API will ensure to replace an empty tuple with NULL
.
I see it as an application of https://en.wikipedia.org/wiki/Robustness_principle (Be conservative in what you send, be liberal in what you accept): PyCall_Vectorcall
should accept an empty tuple but it should not send an empty tuple to the vectorcall function.
pep-0590.rst
Outdated
|
||
Argument Clinic [4]_ automatically generates wrapper functions around lower-level callables, providing safe unboxing of primitive types and | ||
other safety checks. | ||
Argument Clinic could be extended to generate wrapper objects conforming to the new ``vectorcall`` protocol. | ||
Argument Clinic could be extended to generate wrapper objects with the ``METH_VECTORCALL`` signature. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave this as it is. METH_VECTORCALL
is not equivalent to vectorcall
.
Although simpler, it is no faster as the additional check is only required during error handling. |
Not only during error handling. It's relevant for functions that can take keyword arguments but which are called without keyword arguments.
What would "more general" even mean here? The protocol should be as simple to use as possible and not allowing empty tuples makes it simpler. What's wrong with disallowing an empty tuple? |
I pushed an update with some of the suggested edits. |
Workflow/process note: in the future, please don't force-push to PRs. It makes the changes harder to review. |
Mark, Petr: do you agree with all the changes that you did not comment on? |
I changed my mind here. Let's allow I would also like to add a macro I already made these changes in a new commit. |
I replaced |
Could this PR be split into smaller PRs, please? It makes it a lot easier to merge if distinct changes are in different PRs. Also, can you separate the clarifications from the modifications, so that the clarifications can be merged without needing to discuss the merits of the various modifications. |
Putting the non-controversial changes in one PR first, and getting them merged, should help. |
I'd like to come back to this. While working on the implementation, I found the name
For example:
Now the function under discussion executes the operation |
At this point, it's not clear to me what are the controversial changes (for example, I addressed some comments by you two yesterday but I don't know if you're happy with those changes). As far as I can tell, the major points under discussion are:
Then some bikeshedding:
Is there something not on this list that you disagree with? |
I tried to compile a list of the changes here (and which ones are settled).
|
I don't plan any further changes to this PR for now. If it's clear to you what's controversial and what not, feel free to make those changes. |
Alright. It'll take me some time to sort through this. |
Thanks for that list. I'll briefly comment on the unchecked boxes:
Being discussed already, you know my opinion. Good to revert for now if you don't agree.
Bikeshedding, I don't care much.
Bikeshedding, I don't care much.
Note that the actual specification is not changed, only the wording. I changed it because I found the original text unclear. Feel free to do whatever you want with this.
Under discussion, would be good to revert for now.
Both of these are meant as optional things that could possibly be done as part of the implementation but not strictly part of the PEP. They won't be in the initial implementation and they may or may not be done later. I'd still like to keep them in the PEP if only to have some place where these ideas are written down. You could group these in a section "Possible future CPython changes" or something.
Related to the discussion about
I didn't want to claim performance improvements without evidence. Maybe the best would be to remove this section completely?
What's controversial about this? bpo-29259 is very close to PEP 590, so it would be good to mention it somewhere. |
I agree with using the PEP for listing the "optional things we'd like to get into Python 3.8 if we can make it" (as long as they're marked as optional). |
All the points are covered in GH-1035 now; I'll keep track of them until they're resolved. Discussions can continue here. |
Intermediate result of discussions from: * #1028 * #1035 Co-Authored By: Jeroen Demeyer <[email protected]>
548e864
to
85dd0c9
Compare
About
|
To make the |
|
|
Yes, it does have access to the callable object, since it's called with the static PyObject *
myfunc(PyObject *callable, PyObject *const *args,
Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *self = PyCFunction_GetSelf(callable);
nargs = PyVectorcall_NARGS(nargs);
/* From this point on, everything is exactly the same as
* METH_FASTCALL|METH_KEYWORDS */
}
I'm not saying that this should be the preferred way. I'm just saying: why not allow it? We need the functionality anyway for Argument Clinic, so it's not additional effort to support it. |
Actually, scratch that. You need to support two types ( So now I would suggest the minimal approach: leave |
Done now. Closing this PR as everything is now on separate PRs. |
Rewrites/clarify some parts of PEP 590 and change some details, adding myself as author.
Main changes:
kwnames
must be eitherNULL
or a non-empty tuple (this is easier for the callee to detect the case of no keyword arguments).PyCall_MakeTpCall
(what's the use case for making this public API?)METH_VECTORCALL
to mean that the signature is of typevectorcall
(using the actualvectorcall
signature is more natural and faster since you don't need a wrapper at all)PyObject_Vectorcall
, allow passing a keyword dict (better to do conversion from dict to tuple in one place, instead of requiring every caller to do that)PyObject *const *args
(it's already like that in the implementation)Details:
tp_vectorcall_offset
toPy_ssize_t
(whyuintptr_t
? every other offset also has typePy_ssize_t
)PyVectorcall_NARGS(n)
forn & ~PY_VECTORCALL_ARGUMENTS_OFFSET
(cleaner and more future-proof in case we ever add more special bits)PyVectorcall_Function(obj)
to return thevectorcallfunc
pointer stored inobj
(orNULL
).PyTupleObject
in signature toPyObject
(it's already like that in the implementation, passing a specific struct in the Python/C API is normally not done)PY_VECTORCALL_ARGUMENTS_OFFSET
->PY_VECTORCALL_PREPEND
(I didn't like the word "offset" for that, while "prepend" refers to_PyObject_Call_Prepend
)vectorcall
->vectorcallfunc
(analogous to other function pointer typedefs likebinaryfunc
)PyCall_MakeVectorCall
->PyVectorcall_Call
CC @encukou @markshannon
See also the new implementation at python/cpython#13185