-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
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
Remove deprecated fractions.gcd() #83531
Comments
I believe there is a regression here. numpy fails to build with Python 3.9.0a3. ________________________ TestMatmul.test_matmul_object _________________________
f = <numpy.vectorize object at 0x7ff9bc6751f0> numpy/lib/function_base.py:2091: in __call__ cls = <class 'fractions.Fraction'>, numerator = 483, denominator = 809
>>> Fraction(10, -8)
Fraction(-5, 4)
>>> Fraction(Fraction(1, 7), 5)
Fraction(1, 35)
>>> Fraction(Fraction(1, 7), Fraction(2, 3))
Fraction(3, 14)
>>> Fraction('314')
Fraction(314, 1)
>>> Fraction('-35/4')
Fraction(-35, 4)
>>> Fraction('3.1415') # conversion from numeric string
Fraction(6283, 2000)
>>> Fraction('-47e-2') # string may include a decimal exponent
Fraction(-47, 100)
>>> Fraction(1.47) # direct construction from float (exact conversion)
Fraction(6620291452234629, 4503599627370496)
>>> Fraction(2.25)
Fraction(9, 4)
>>> Fraction(Decimal('1.47'))
Fraction(147, 100)
"""
self = super(Fraction, cls).__new__(cls)
if denominator is None:
if type(numerator) is int:
self._numerator = numerator
self._denominator = 1
return self
elif isinstance(numerator, numbers.Rational):
self._numerator = numerator.numerator
self._denominator = numerator.denominator
return self
elif isinstance(numerator, (float, Decimal)):
# Exact conversion
self._numerator, self._denominator = numerator.as_integer_ratio()
return self
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0')
denom = m.group('denom')
if denom:
denominator = int(denom)
else:
denominator = 1
decimal = m.group('decimal')
if decimal:
scale = 10**len(decimal)
numerator = numerator * scale + int(decimal)
denominator *= scale
exp = m.group('exp')
if exp:
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-':
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
elif type(numerator) is int is type(denominator):
pass # *very* normal case
elif (isinstance(numerator, numbers.Rational) and
isinstance(denominator, numbers.Rational)):
numerator, denominator = (
numerator.numerator * denominator.denominator,
denominator.numerator * numerator.denominator
)
else:
raise TypeError("both arguments should be "
"Rational instances")
if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
if _normalize:
if type(numerator) is int is type(denominator):
# *very* normal case
g = math.gcd(numerator, denominator)
if denominator < 0:
g = -g
else:
> g = _gcd(numerator, denominator)
E NameError: name '_gcd' is not defined
__class__ = <class 'fractions.Fraction'>
_normalize = True
cls = <class 'fractions.Fraction'>
denominator = 809
numerator = 483
self = <[AttributeError("_numerator") raised in repr()] Fraction object at 0x7ff9bc6753a0>
/usr/lib64/python3.9/fractions.py:164: NameError This removed _gcd, but it is still called in: Line 164 in 58a4054
|
Reproducer: class myint(int):
def __mul__(self, other):
return type(self)(int(self)*int(other))
@property
def numerator(self):
return type(self)(super().numerator)
@property
def denominator(self):
return type(self)(super().denominator) Before: >>> fractions.Fraction(myint(1), myint(2))
Fraction(1, 2) After: >>> fractions.Fraction(myint(1), myint(2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.9/fractions.py", line 164, in __new__
g = _gcd(numerator, denominator)
NameError: name '_gcd' is not defined |
The relevant piece of Python code from fractions.py looks like this: if type(numerator) is int is type(denominator):
# *very* normal case
g = math.gcd(numerator, denominator)
if denominator < 0:
g = -g
else:
g = _gcd(numerator, denominator) I wonder whether we should simply drop the type check: math.gcd should accept anything that supports __index__ (and that includes NumPy integer types and anything that implements numbers.Integral). OTOH, math.gcd will return a plain int, and if we have some subclass myint of int, we might want all the arithmetic to stay in the domain of myints (though I'm not really sure _why_ we'd want that). In that case we'll need to restore the pure Python _gcd implementation. |
Naively implementing this code, I'd use isinstance(numerator, int) over type(numerator) is int. Is that strict type check really needed? I could not find anywhere whether numerator and denominator of numbers.Rational need to be numbers.Integral. I would expect so, however this is not written in the documentation of numbers.Rational. If that's the case, I think checking it for being numbers.Integral and failing hard if not is a reasonable thing to do. |
Apologies, I think I moved the discussion off-track. The first order of business should be to fix the regression. We can discuss behaviour changes after that. I've opened #62509 as a quick fix for the regression. |
I tried but failed to write a test to mimick numpy.int64 type. I tried to build a type which implements numbers.Rational but don't inherit from int, but there are way too many methods that I have to implement :-( Morever, installing numpy on a Python 3.9 virtual environment is quite tricky. Lhe latest Cython release (0.29.14) isn't compatible with Python 3.9. Miro gave me a command to install Cython on Python 3.9: python -m pip install https://github.com/cython/cython/archive/master.tar.gz --install-option="--no-cython-compile" But then "pip install numpy" tries to reinstall Cython which fails :-/ |
I managed to test my PR with numpy: $ env/bin/python
>>> import fractions
>>> import numpy
>>> f=fractions.Fraction(numpy.int64(1*3), numpy.int64(2*3))
>>> f
Fraction(1, 2)
>>> type(f.numerator)
<class 'numpy.int64'>
>>> type(f.denominator)
<class 'numpy.int64'> So it works as expected: numerator and denominator have the expected type, and there is no exception ;-) I used the following commands to get numpy in a Python 3.9 virtual environment: ./python -m venv env |
FYI the full numpy test suite pass with PR 18375: (env) vstinner@apu$ python runtests.py -v |
Thanks Miro for the bug report and thanks Mark for the great review and discussion! It should now be fixed. I tested manually that my change fix numpy test suite. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: