-
-
Notifications
You must be signed in to change notification settings - Fork 31k
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
bpo-39350: Fix regression introduced when fractions.gcd was removed #18309
Conversation
The broken code made it to 3.9.0a3. That's how I have discovered the regression. |
Whoops; you're right. News entry needed, then! |
Codecov Report
@@ Coverage Diff @@
## master #18309 +/- ##
===========================================
+ Coverage 82.11% 83.19% +1.07%
===========================================
Files 1955 1570 -385
Lines 588596 414126 -174470
Branches 44401 44402 +1
===========================================
- Hits 483346 344525 -138821
+ Misses 95605 59963 -35642
+ Partials 9645 9638 -7
Continue to review full report at Codecov.
|
@@ -13,6 +13,12 @@ | |||
__all__ = ['Fraction'] | |||
|
|||
|
|||
def _gcd(a, b): |
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.
Looks good, what about declared this function as the nested function?
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.
In this case, we can skip the news :)
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.
-def _gcd(a, b):
- # Supports non-integers for backward compatibility.
- while b:
- a, b = b, a%b
- return a
-
# Constants related to the hash implementation; hash(x) is based
# on the reduction of x modulo the prime _PyHASH_MODULUS.
_PyHASH_MODULUS = sys.hash_info.modulus
@@ -98,6 +92,11 @@ class Fraction(numbers.Rational):
"""
self = super(Fraction, cls).__new__(cls)
+ def _gcd(a, b):
+ while b:
+ a, b = b, a%b
+ return a
+
if denominator is None:
if type(numerator) is int:
self._numerator = numerator
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.
Looks good, what about declared this function as the nested function?
I'd prefer to revert to as close as possible to the previous code, which had _gcd
as a module-level function.
I think we do need a news entry, because this is fixing a bug that was already made available in a release. If the bug had been introduced and then fixed without any intermediate release, then we could have skipped the news entry.
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.
Got it :)
FYI I'm now going to patch our Python we use to test Fedora packages with and will see if it actually fixes numpy (I am quite confident it will). |
it actually fixes numpy |
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 like this as a fast one time fix of a regression with a test. Later, we can decide whether to redo the code not to use this _gcd
function, but that is out of scope here.
@vstinner Okay to merge this? |
I don't see the need for _gcd(). I'm not sure that we have to keep the original type to compute the gcd. fractions.py code:
I don't think that it's a crazy requirement to support I proposed PR #18375 which always use math.gcd(). |
@vstinner: Sure, but that's a behaviour change. I think we should fix the regression first (i.e., take the behaviour back to what it was, but with |
|
||
class myint(int): | ||
def __mul__(self, other): | ||
return myint(int(self)*int(other)) |
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.
return myint(int(self)*int(other)) | |
return myint(int(self) * int(other)) |
def _gcd(a, b): | ||
# Supports non-integers for backward compatibility. | ||
while b: | ||
a, b = b, a%b |
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.
a, b = b, a%b | |
a, b = b, a % b |
@@ -13,6 +13,12 @@ | |||
__all__ = ['Fraction'] | |||
|
|||
|
|||
def _gcd(a, b): | |||
# Supports non-integers for backward compatibility. |
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.
My main worry with this PR is this comment. I don't think that we should keep it back "backward compatibility". Either we remove _gcd() or we keep it on deliberate purpose, to return the same type than inputs (a and b arguments).
I understand that @mdickinson wants to return the same type, and I'm fine with that. But in this case, the comment should be something like:
For int sublcasses and other types, math.gcd(a, b) call the __index__() method on arguments
and it always return the int type. This function is used to return the same type than input.
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.
Maybe also add a comment in Fraction constructor to explain why we test type() == int: explain that we want to get g as the same type than numerator and denominator types. Or something like that ;-)
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.
@vstinner This comment wasn't introduced in this PR; it's from the previously working code.
I understand that @mdickinson wants to return the same type
No. I simply want to undo the breakage that #18021 introduced, in the simplest way possible, and with a regression test. I'm open to discussing any sort of behaviour change after that, but the first priority should be to get back to a working state, and the simplest path is to get back to the working state that existed in 3.9.0a2.
Another way to address the breakage would be to revert #18021 in its entirety, and then re-do it more carefully.
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.
The statistics module tries to keep input types for the output type. I think that it's good thing for generic modules like fractions and statistics. The documentation seems to say that it keeps the input type, but it's not very explicit: https://docs.python.org/dev/library/fractions.html
I'm fine with keeping Python 3.8 behavior (so use your PR). If someone wants to change it, it can be done later.
My first intent was only to remove the public deprecated fractions.gcd() function. I don't care about private functions like fractions._gcd().
return myint(1) | ||
|
||
myhalf = F(myint(1), myint(2)) | ||
self.assertEqual(myhalf, F(1, 2)) |
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.
Can please add the following tests?
self.assertEqual(f.numerator, 1)
self.assertEqual(f.denominator, 2)
self.assertEqual(type(f.numerator), myint)
self.assertEqual(type(f.denominator), myint)
def denominator(self): | ||
return myint(1) | ||
|
||
myhalf = F(myint(1), myint(2)) |
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 would prefer to see GCD being computed and used, so I propose:
myhalf = F(myint(1), myint(2)) | |
myhalf = F(myint(1 * 3), myint(2 * 3)) |
class myint(int): | ||
def __mul__(self, other): | ||
return myint(int(self)*int(other)) | ||
|
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.
Can please add:
def __floordiv__(self, other):
return type(self)(int(self) // int(other))
def __mod__(self, other):
return type(self)(int(self) % int(other))
@@ -0,0 +1,3 @@ | |||
Revert removal of ``fractions._gcd``. That removal caused breakage when | |||
using ``fractions.Fraction`` with integer-like values (for example NumPy | |||
integers). |
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.
You may reuse my NEWS entry to avoid mentioning the private _gcd() method, I don't think that it matters here:
Fix regression in :class:`fractions.Fraction` if the numerator and/or the
denominator is an :class:`int` subclass.
When you're done making the requested changes, leave the comment: |
I would prefer to only change the code once. Either remove _gcd() and use math.gcd() which returns an int for all input types (behavior change): PR #18375, or don't use math.gcd() for types others than int: this PR. |
#18375 obviates the need for this fix |
I merged my PR #18375. |
This PR is a quick fix for the breakage introduced in #18021, which removed the
_gcd
function while the core code was still using it.Possibly the behaviour of
Fraction
for non-integers should be different, but I think it makes sense to fix the breakage immediately and then discuss whether we want to make behavioural changes.No news entry needed, because the broken code hasn't make it into any release or prerelease.(EDIT: not true, unfortunately : @hroncok pointed out in comments below that the regression was released in 3.9.0a3)https://bugs.python.org/issue39350