mirror of https://github.com/python/cpython
gh-82017: Support as_integer_ratio() in the Fraction constructor (GH-120271)
Any objects that have the as_integer_ratio() method (e.g. numpy.float128) can now be converted to a fraction.
This commit is contained in:
parent
eaf094c09b
commit
c8d2630995
|
@ -17,25 +17,30 @@ The :mod:`fractions` module provides support for rational number arithmetic.
|
|||
A Fraction instance can be constructed from a pair of integers, from
|
||||
another rational number, or from a string.
|
||||
|
||||
.. index:: single: as_integer_ratio()
|
||||
|
||||
.. class:: Fraction(numerator=0, denominator=1)
|
||||
Fraction(other_fraction)
|
||||
Fraction(float)
|
||||
Fraction(decimal)
|
||||
Fraction(number)
|
||||
Fraction(string)
|
||||
|
||||
The first version requires that *numerator* and *denominator* are instances
|
||||
of :class:`numbers.Rational` and returns a new :class:`Fraction` instance
|
||||
with value ``numerator/denominator``. If *denominator* is ``0``, it
|
||||
raises a :exc:`ZeroDivisionError`. The second version requires that
|
||||
*other_fraction* is an instance of :class:`numbers.Rational` and returns a
|
||||
:class:`Fraction` instance with the same value. The next two versions accept
|
||||
either a :class:`float` or a :class:`decimal.Decimal` instance, and return a
|
||||
:class:`Fraction` instance with exactly the same value. Note that due to the
|
||||
raises a :exc:`ZeroDivisionError`.
|
||||
|
||||
The second version requires that *number* is an instance of
|
||||
:class:`numbers.Rational` or has the :meth:`!as_integer_ratio` method
|
||||
(this includes :class:`float` and :class:`decimal.Decimal`).
|
||||
It returns a :class:`Fraction` instance with exactly the same value.
|
||||
Assumed, that the :meth:`!as_integer_ratio` method returns a pair
|
||||
of coprime integers and last one is positive.
|
||||
Note that due to the
|
||||
usual issues with binary floating-point (see :ref:`tut-fp-issues`), the
|
||||
argument to ``Fraction(1.1)`` is not exactly equal to 11/10, and so
|
||||
``Fraction(1.1)`` does *not* return ``Fraction(11, 10)`` as one might expect.
|
||||
(But see the documentation for the :meth:`limit_denominator` method below.)
|
||||
The last version of the constructor expects a string or unicode instance.
|
||||
|
||||
The last version of the constructor expects a string.
|
||||
The usual form for this instance is::
|
||||
|
||||
[sign] numerator ['/' denominator]
|
||||
|
@ -110,6 +115,10 @@ another rational number, or from a string.
|
|||
Formatting of :class:`Fraction` instances without a presentation type
|
||||
now supports fill, alignment, sign handling, minimum width and grouping.
|
||||
|
||||
.. versionchanged:: 3.14
|
||||
The :class:`Fraction` constructor now accepts any objects with the
|
||||
:meth:`!as_integer_ratio` method.
|
||||
|
||||
.. attribute:: numerator
|
||||
|
||||
Numerator of the Fraction in lowest term.
|
||||
|
|
|
@ -100,6 +100,13 @@ ast
|
|||
|
||||
(Contributed by Bénédikt Tran in :gh:`121141`.)
|
||||
|
||||
fractions
|
||||
---------
|
||||
|
||||
Added support for converting any objects that have the
|
||||
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
|
||||
(Contributed by Serhiy Storchaka in :gh:`82017`.)
|
||||
|
||||
os
|
||||
--
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
"""Fraction, infinite-precision, rational numbers."""
|
||||
|
||||
from decimal import Decimal
|
||||
import functools
|
||||
import math
|
||||
import numbers
|
||||
|
@ -244,7 +243,9 @@ class Fraction(numbers.Rational):
|
|||
self._denominator = numerator.denominator
|
||||
return self
|
||||
|
||||
elif isinstance(numerator, (float, Decimal)):
|
||||
elif (isinstance(numerator, float) or
|
||||
(not isinstance(numerator, type) and
|
||||
hasattr(numerator, 'as_integer_ratio'))):
|
||||
# Exact conversion
|
||||
self._numerator, self._denominator = numerator.as_integer_ratio()
|
||||
return self
|
||||
|
@ -278,8 +279,7 @@ class Fraction(numbers.Rational):
|
|||
numerator = -numerator
|
||||
|
||||
else:
|
||||
raise TypeError("argument should be a string "
|
||||
"or a Rational instance")
|
||||
raise TypeError("argument should be a string or a number")
|
||||
|
||||
elif type(numerator) is int is type(denominator):
|
||||
pass # *very* normal case
|
||||
|
|
|
@ -354,6 +354,41 @@ class FractionTest(unittest.TestCase):
|
|||
self.assertRaises(OverflowError, F, Decimal('inf'))
|
||||
self.assertRaises(OverflowError, F, Decimal('-inf'))
|
||||
|
||||
def testInitFromIntegerRatio(self):
|
||||
class Ratio:
|
||||
def __init__(self, ratio):
|
||||
self._ratio = ratio
|
||||
def as_integer_ratio(self):
|
||||
return self._ratio
|
||||
|
||||
self.assertEqual((7, 3), _components(F(Ratio((7, 3)))))
|
||||
errmsg = "argument should be a string or a number"
|
||||
# the type also has an "as_integer_ratio" attribute.
|
||||
self.assertRaisesRegex(TypeError, errmsg, F, Ratio)
|
||||
# bad ratio
|
||||
self.assertRaises(TypeError, F, Ratio(7))
|
||||
self.assertRaises(ValueError, F, Ratio((7,)))
|
||||
self.assertRaises(ValueError, F, Ratio((7, 3, 1)))
|
||||
# only single-argument form
|
||||
self.assertRaises(TypeError, F, Ratio((3, 7)), 11)
|
||||
self.assertRaises(TypeError, F, 2, Ratio((-10, 9)))
|
||||
|
||||
# as_integer_ratio not defined in a class
|
||||
class A:
|
||||
pass
|
||||
a = A()
|
||||
a.as_integer_ratio = lambda: (9, 5)
|
||||
self.assertEqual((9, 5), _components(F(a)))
|
||||
|
||||
# as_integer_ratio defined in a metaclass
|
||||
class M(type):
|
||||
def as_integer_ratio(self):
|
||||
return (11, 9)
|
||||
class B(metaclass=M):
|
||||
pass
|
||||
self.assertRaisesRegex(TypeError, errmsg, F, B)
|
||||
self.assertRaisesRegex(TypeError, errmsg, F, B())
|
||||
|
||||
def testFromString(self):
|
||||
self.assertEqual((5, 1), _components(F("5")))
|
||||
self.assertEqual((3, 2), _components(F("3/2")))
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Added support for converting any objects that have the
|
||||
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
|
Loading…
Reference in New Issue