-
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
Add PyLong_Sign() public function #19
Comments
IIRC @markshannon has opinions on the API for long ints. Please ask him first. |
What's the use case for this? Why pass in a |
This is something we like to have to support int<->mpz conversion of "big enough" int's with mpz_import/export. See current code in the gmpy2 (similar approach has Sage): for reading and for writing. Now we have Together with Edit, a complete interface: /* reading */
int PyLong_Sign(PyLongObject *obj); // mpz_sgn()
Py_ssize_t PyLong_DigitCount(PyLongObject *obj); // mpz_size()
const digit * PyLong_AsDigits(PyLongObject *obj); // mpz_limbs_read()
/* writing (former _PyLong_FromDigits, used in _decimal.c) */
PyLongObject* PyLong_FromDigits(int negative, Py_ssize_t digit_count, digit *digits);
This is something I was thinking first. Clearly, it's enough for above use case. If this kind of API is acceptable (so far, I see only |
A code search on PyPI top 5,000 projects (at 2023-11-15) finds usage in 10 projects:
Code:
One usage is to ... test the sign of a number :-) Examples: bool neg = _PyLong_Sign(obj) < 0; and if (_PyLong_Sign(value) >= 0) ...
For consistency with other PyLong APIs. Example: Generic |
JFR, it was discussed rather in the pr thread: python/cpython#106005 Perhaps, this is the relevant issue: capi-workgroup/api-evolution#29 |
By the way, I'm surprised that the C API has no function to compare a Python int object to a C integer, something like:
I'm mentiong such hypothetical function since It could be done with: int cmp;
if (PyLong_CompareWithLong(number, 0, &cmp) < 0) ... handle error ...
if (cmp == 0) ... equal to zero
else if (cmp < 0) ... smaller than zero
else ... greater than zero Another data: I just added int cmp;
PyObject *zero = Py_GetConstantBorrowed(Py_CONSTANT_ONE);
cmp = PyObject_RichCompareBool(number, zero);
if (cmp < 0 && PyErr_Occurred()) ... handle error ...
if (cmp == 0) ... equal to zero
else if (cmp < 0) ... smaller than zero ...
else ... greater than zero ... But I dislike this code, since it uses a borrowed reference and it requires to call |
@encukou @gvanrossum @iritkatriel @zooba: What's your opinion on adding |
+1 from me. There was no formal vote, but most members of the WG decided to use
For “evolution”, let's stick to that. |
Hopefully I'm good with adding the function, and I'm about 50/50 between Victor's proposed API and one that returns a "switch"-able result:
Though honestly, none of the times when I've needed this have I needed to optimise for a sign check before conversion, especially since for compact values it'll cost about the same to assign |
In general I find APIs with output variables awkward, so I want to use them only when necessary. Since the sign function has only four possible outcomes (error, negative, zero, positive) I feel it doesn't really warrant the output variable, so I'm supportive of Steve's solution. |
I agree, with -1 for error and non-negative values for the three valid outcomes. |
I'm fine with any API as soon as it doesn't require to call PyErr_Occurred(). If we go with the enum-like approach, I suggest to use more specific names, add "IS_" in the name, and use Py_ prefix rather than PY_: #define Py_LONG_IS_ZERO 0
#define Py_LONG_IS_NEGATIVE 1
#define Py_LONG_IS_POSITIVE 2 Usage would be: int sign = PyLong_Sign(obj);
if (sign < 0) { ... error ...; }
if (sign == Py_LONG_IS_ZERO || sign == Py_LONG_IS_POSITIVE) {
... obj >= 0 ...
}
if (sign == Py_LONG_IS_NEGATIVE) {
... obj < 0 ...
} The advantage of int sign;
if (PyLong_Sign(obj, &sign) < 0) { ... error ...; }
if (sign >= 0) {
... obj >= 0 ...
}
if (sign < 0) {
... obj < 0 ...
} The gmpy project needs to check if a number is negative: https://github.com/aleaxit/gmpy/blob/eb8dfcbd84abcfcb36b4adcb0d5c6d050731dd75/src/gmpy2_convert_gmp.c#L41-L75
So any API would be fine. |
How about we define the result as follows?
|
I think it will be confusing in light of the c api convention of <0 indicating error. That's probably stronger in this case than the convention on comparison result being -1,0,1. |
PyObject_RichCompareBool() API doesn't have this issue since it takes two arguments and a 3rd is the comparison operator:
|
I very nearly suggested this myself. I'm okay with it, apart from not really being convinced this function is special enough to justify the -2. A near equivalent could be:
Such that |
Okay, so how about
Then you can calculate the "classic" |
This might be too much of a detour, but:
AFAICS, this can be shuffled a bit so that you can get a classic
(Assuming the ergonomics are worth a few extra CPU instructions -- e.g. with |
I'd avoid the "slightly strange" return values. There is a good reason for them being the way they are, but it probably only makes sense to use them internally. I still don't why we need an error value at all. All integers have a sign and this function will never need to allocate any memory. I still think |
Using |
Because we don't have a strongly typed public API, we have no way to know that the incoming value is an int. We need to check the type and return something if the API is misused - reading arbitrary memory isn't okay. Unfortunately, the intended "abstract API" vs "concrete API" distinction has long given up on assuming input types. The only "lower level API" we have is anything marked private, which is what we're changing here. We can keep the private API that assumes the type, but the public one has to check and handle it safely. |
You could argue that someone could cast a We have to assume a basic level of competence in the users of the C API, so why not here? |
By "we don't have" I meant in general, our public API is not strongly typed like that. We don't ever expect users of the API to handle types other than Yes we could add it, but we can't change what's already there, and so we decided that it's not going to be a useful evolution of the language. If we decide that a strongly typed C interface is valuable in a new API design (not a given, since we're being far more considerate of non-C users), then in a revamp we may well expose more C types as part of the API. But for now, we're not going in that direction. So that's "why not here". |
Are there no parts of the C API where we specify a function that takes a specific object type as argument and returns a value without possibility of error? I looked for this in the API docs and found a smattering, e.g. It would be a novelty for the So in the end I agree with Steve. |
If we count Unstable C API, then there are
I doubt this function has much sense alone. So, please consider other possible extensions of the public API, mentioned above (e.g. |
Yep. Alas, using
Incidentally, I'm (slowly) collecting this kind of data for my Language Summit topic, which will be about adding the kind of API Mark wants in a consistent way.
But we can also look at all functions that take a specific object. There aren't that many, and the vast majority of them deal with Click for a list(From a Clang AST dump, ad-hoc filtering. Macros aren't included at all; no info about how wrongly cast arguments are handled.)
Counts of tokens in the above argument types:
These are functions starting with
|
I think we can reasonably hypothesise (and perhaps Guido can confirm) that the Anything allocated or instantiated by CPython would be I suspect the other functions taking more specific object types directly were either added without considering this principle or were being consistent with an earlier API that returned non-
If we do go this way, we should also define |
Yes, exactly.
More likely, the Maybe for 3.14 we can sit down with Mark and someone from (e.g.) GMP and design a brand new API for |
FWIW, I still prefer the original proposal to the 0/1/2. Looking at the guidelines proposal I've been writing, I'd generally like to go for a certain strict, even mechanical consistency, plus sometimes adding an extra variant that trades that consistency for convenience or speed. So, why not both?
|
Why not both? Because this use case isn't important enough to have two separate APIs with different signature and behavior. I can live with having just the two-argument version if we rename it to |
If we do provide both, |
If we were to do both, the latter ought to be Otherwise, 100% agree with Guido (and 90% with Mark: I think the pointer cast will sometimes be less convenient than chaining conditions ( |
Sounds great!
That's OK -- with a name that announces that this function is unusual.
I don't think we need to worry about API stability here. IMO, the convenient API is inconsistent with how we want new API to look. While consistency and stability are correlated, I'd rather keep them separate ( |
On success, set On failure, return -1 with an exception set. |
Have any of the likely users asked for this two argument form, as opposed to |
It hardly matters, they aren't getting one that accepts |
@iritkatriel, what are your thoughts? |
@erlend-aasland: Are you ok with API proposed in #19 (comment) ? Tell me if you need more context/information. And it's ok if you need more time to decide. |
I'm fine with the proposed API; I voted. |
Thanks all. The SC adopted |
I concur with @markshannon. For such low-level function the overhead of checking the type is too high, and it makes the API less convenient. Also, I think that These functions are used when performance is very important. When it is not important, you can use |
In which kind of code the performance of PyLong_GetSign() would matter? I don't expect this function to be called often, like not more than once per integer, and not in "hot code". PyLong_GetSign() cannot fail if the argument is a Python int. If you know that the parameter is a Python int, you can simply ignore the error handling (maybe using an assertion, just in case). |
I do not remember details, it was many years ago, but I used BTW, the The API proposed here is not only slower, it is less convenient. You need to introduce a variable, and even if you ignore error, you cannot use it in expression. int sign;
(void)PyLong_Sign(obj, &sign);
if (sign < 0) { instead of if (PyLong_IsNegative(obj)) { Multiply this by 112 use cases. |
These aspects were taken in account in the decision. This API takes Also, this issue is not closed, so I suggest to open a new one. |
API:
int PyLong_Sign(PyObject *obj, int *sign)
Retrieve the sign of integer object obj (
0
,-1
or+1
for zero,negative or positive integer, respectively) in a variable sign.
Return
0
on success, else-1
with an exception set. This functionalways succeeds if obj is a :c:type:
PyLongObject
or its subtype.PR: python/cpython#116561
I would like to propose adding the API directly to the limited API, what do you think?
The text was updated successfully, but these errors were encountered: