bpo-33073: Adding as_integer_ratio to ints. by lisroach · Pull Request #8750 · python/cpython (original) (raw)
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 andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Conversation47 Commits18 Checks0 Files changed
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.Learn more about bidirectional Unicode characters
[ Show hidden characters]({{ revealButtonHref }})
PyObject *result_pair = NULL; |
---|
denominator = PyLong_FromLong(1); |
result_pair = PyTuple_Pack(2, self, denominator); |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If PyLong_FromLong
returns NULL
then PyTuple_Pack
and Py_DECREF(denominator)
will fail.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the whole function can be replaced with return PyTuple_Pack(2, self, _PyLong_One)
.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pablogsal But PyLong_FromLong always receive the 1. How could it fail?
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contract of PyLong_FromLong
says that it can return NULL
on failure. Even if the current implementation (that uses the array of small ints) makes it improbable/impossible to fail, this can change in the future without changing the external API.
Return a pair of integers, whose ratio is exactly equal to the original int |
---|
and with a positive denominator. |
Raise OverflowError on infinities and a ValueError on NaNs. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An int can never be any of these things - remove this line
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. math.inf
is float class
def test_as_integer_ratio(self): |
---|
tests = [10, 0, -10, 1, 3] |
for value in tests: |
self.assertEqual((value).as_integer_ratio(), (value, 1)) |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no parens needed around value
Return a pair of integers whose ratio is exactly equal to the original integer |
---|
and with a positive denominator. The integer ratio of integers (whole numbers) |
is always the integer as the numerator and 1 as the denominator. |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A versionadded
directive needs to be added. Also, the developer's guide states that reST files should use an indentation of 3 spaces.
int_as_integer_ratio_impl(PyObject *self) |
---|
/*[clinic end generated code: output=e60803ae1cc8621a input=ce9c7768a1287fb9]*/ |
{ |
return PyTuple_Pack(2, self, _PyLong_One) |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a missing semicolon at the end of this line; I think that's why the continuous integration builds are failing.
Also, please change the indentation of this line to 4 spaces instead of 2 spaces, following PEP7. Thanks!
@lisroach Thanks for the update. I've pushed a commit that should fix the failing test_doctest.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lisroach If you're okay with my recent updates, I think this PR is ready to merge.
int_as_integer_ratio_impl(PyObject *self) |
---|
/*[clinic end generated code: output=e60803ae1cc8621a input=c1aea0aa6fb85c28]*/ |
{ |
return PyTuple_Pack(2, self, _PyLong_One); |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True.as_integer_ratio()
will return (True, 1)
. It should return (1, 1)
.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm; good point. There's a precedent here in the form of True.numerator
, which returns 1
.
Return a pair of integers, whose ratio is exactly equal to the original int |
---|
and with a positive denominator. |
>>> (10).as_integer_ratio() |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need so much examples in a docstring?
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's useful to have at least one positive and one negative example, so that it's obvious at a glance that the behaviour for negatives is to give (for example) (-5, 1)
rather than (5, -1)
. I could imagine users thinking that 0
was somehow a special case, too, so I like that we have the 0
example there.
@@ -1349,6 +1349,11 @@ def test_shift_bool(self): |
---|
self.assertEqual(type(value << shift), int) |
self.assertEqual(type(value >> shift), int) |
def test_as_integer_ratio(self): |
tests = [10, 0, -10, 1, 3] |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why so much similar cases are needed?
Add tests for booleans and other int subclasses. Check types of numerator and denominator.
Return a pair of integers whose ratio is exactly equal to the original |
integer and with a positive denominator. The integer ratio of integers |
(whole numbers) is always the integer as the numerator and 1 as the |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
``1``
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing my approval due to the issues Serhiy noted. @lisroach do you want to tackle the outstanding comments? I'm happy to pick this up if not.
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.
Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again
. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.
I have made the requested changes; please review again.
I am not sure if I could do the boolean check better- somehow in one line? I'm open to advice!
Thanks for all the reviews @mdickinson and @serhiy-storchaka it's been really helpful!
@eric-wieser I believe Serhiy's thinking is correct, it should be (1, 1).
Subclasses of int inherit the same operations of int, and those operations create instances of int (as opposed to creating an instance of the subclass). For example, if we look at the unary plus of int:
class Int(int): ... pass ... x = Int(42) type(+x) <class 'int'> type(+True) <class 'int'>
class Foo(enum.IntEnum): |
---|
X = 42 |
self.assertEqual(Foo.X.as_integer_ratio(), (42, 1)) |
assert(type(Foo.X.as_integer_ratio()[0] == int)) |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be: self.assertEqual(type(Foo.X.as_integer_ratio()[0], int)
. Unittest doesn't use assertion statements.
return PyTuple_Pack(2, self, _PyLong_One); |
---|
else { |
PyObject *temp = PyNumber_Positive(self); |
Py_DECREF(temp); |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DECREF needs to occur after building the tuple; otherwise, the Python integer object can (and likely will) disappear before it gets used.
if PyLong_CheckExact(self) |
---|
return PyTuple_Pack(2, self, _PyLong_One); |
else { |
PyObject *temp = PyNumber_Positive(self); |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need another way to do this. The intent of this code is to construct a regular integer instance from an instance of an int subclass. The problem with PyNumber_Positive() is that the subclass can itself define pos() to return something other than an exact int. Elsewhere, we use _PyLong_Copy for this purpose.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why PyNumber_Index
does not call _PyLong_Copy
?
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.
Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again
. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.
Reviving a comment hidden away above - should operator.index
/ PyNumber_Index
/ int->tp_as_number->nb_index
call _PyLong_Copy
too for consistency?
{ |
---|
if PyLong_CheckExact(self) { |
return PyTuple_Pack(2, self, _PyLong_One); |
} else { |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else {
should be on the next line per PEP 7.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing parens on the if
too
Eric, I didn't see your comments prior to merging. See PR 9297 for the cleanup. The part that was fine as-is was using PyTuple_Pack() on two different code paths.
Agreed, the two code paths thing was unimportant. #9297 seems to address all my comments, other than the question about nb_index
, which is tangential anyway
Can you note your review on 9297 please.
If that's aimed at me, I don't know what you're asking me to do.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR was merged too soon. There are several issues with it. See also comments by @sir-sigurd and @eric-wieser.
@@ -1365,20 +1365,20 @@ def test_as_integer_ratio_maxint(self): |
---|
def test_as_integer_ratio_bool(self): |
self.assertEqual(True.as_integer_ratio(), (1, 1)) |
self.assertEqual(False.as_integer_ratio(), (0, 1)) |
assert(type(True.as_integer_ratio()[0]) == int) |
assert(type(False.as_integer_ratio()[0]) == int) |
self.assertEqual(type(True.as_integer_ratio()[0]), int) |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test also the denumerator type.
if PyLong_CheckExact(self) { |
---|
return PyTuple_Pack(2, self, _PyLong_One); |
} else { |
PyObject *numerator = _PyLong_Copy(self); |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the same same code as for for the numerator
getter.
Test the result for NULL.
@@ -91,6 +91,10 @@ Other Language Changes |
---|
was lifted. |
(Contributed by Serhiy Storchaka in :issue:`32489`.) |
* The ``int`` type now has a new ``as_integer_ratio`` method compatible |
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add links:
:class:int
:meth:~int.as_integer_ratio
:meth:float.as_integer_ratio
Reviving a comment hidden away above - should operator.index / PyNumber_Index / int->tp_as_number->nb_index call _PyLong_Copy too for consistency?
This is a different issue. And I think that it should not.