https://github.com/python/cpython/commit/b52c7306ea4470f9d7548655c2a1b89a07ff5504
commit: b52c7306ea4470f9d7548655c2a1b89a07ff5504
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-10-14T07:54:59Z
summary:
gh-121797: Add class method Fraction.from_number() (GH-121800)
It is an alternative constructor which only accepts a single numeric argument.
Unlike to Fraction.from_float() and Fraction.from_decimal() it accepts any
real numbers supported by the standard constructor (int, float, Decimal,
Rational numbers, objects with as_integer_ratio()).
Unlike to the standard constructor, it does not accept strings.
files:
A Misc/NEWS.d/next/Library/2024-07-15-19-34-56.gh-issue-121797.qDqj59.rst
M Doc/library/fractions.rst
M Doc/whatsnew/3.14.rst
M Lib/fractions.py
M Lib/test/test_fractions.py
diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst
index 2ee154952549ac..fc7f9a6301a915 100644
--- a/Doc/library/fractions.rst
+++ b/Doc/library/fractions.rst
@@ -166,6 +166,16 @@ another rational number, or from a string.
instance.
+ .. classmethod:: from_number(number)
+
+ Alternative constructor which only accepts instances of
+ :class:`numbers.Integral`, :class:`numbers.Rational`,
+ :class:`float` or :class:`decimal.Decimal`, and objects with
+ the :meth:`!as_integer_ratio` method, but not strings.
+
+ .. versionadded:: 3.14
+
+
.. method:: limit_denominator(max_denominator=1000000)
Finds and returns the closest :class:`Fraction` to ``self`` that has
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index c62a3ca5872eef..b22d1bd1e99d4e 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -263,6 +263,10 @@ fractions
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
(Contributed by Serhiy Storchaka in :gh:`82017`.)
+* Add alternative :class:`~fractions.Fraction` constructor
+ :meth:`Fraction.from_number() <fractions.Fraction.from_number>`.
+ (Contributed by Serhiy Storchaka in :gh:`121797`.)
+
functools
---------
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 34fd0803d1b1ab..f0cbc8c2e6c012 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -279,7 +279,8 @@ def __new__(cls, numerator=0, denominator=None):
numerator = -numerator
else:
- raise TypeError("argument should be a string or a number")
+ raise TypeError("argument should be a string or a Rational "
+ "instance or have the as_integer_ratio()
method")
elif type(numerator) is int is type(denominator):
pass # *very* normal case
@@ -305,6 +306,28 @@ def __new__(cls, numerator=0, denominator=None):
self._denominator = denominator
return self
+ @classmethod
+ def from_number(cls, number):
+ """Converts a finite real number to a rational number, exactly.
+
+ Beware that Fraction.from_number(0.3) != Fraction(3, 10).
+
+ """
+ if type(number) is int:
+ return cls._from_coprime_ints(number, 1)
+
+ elif isinstance(number, numbers.Rational):
+ return cls._from_coprime_ints(number.numerator, number.denominator)
+
+ elif (isinstance(number, float) or
+ (not isinstance(number, type) and
+ hasattr(number, 'as_integer_ratio'))):
+ return cls._from_coprime_ints(*number.as_integer_ratio())
+
+ else:
+ raise TypeError("argument should be a Rational instance or "
+ "have the as_integer_ratio() method")
+
@classmethod
def from_float(cls, f):
"""Converts a finite float to a rational number, exactly.
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 4907f4093f52c9..98dccbec9566ac 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -283,6 +283,13 @@ def __repr__(self):
class RectComplex(Rect, complex):
pass
+class Ratio:
+ def __init__(self, ratio):
+ self._ratio = ratio
+ def as_integer_ratio(self):
+ return self._ratio
+
+
class FractionTest(unittest.TestCase):
def assertTypedEquals(self, expected, actual):
@@ -355,14 +362,9 @@ def testInitFromDecimal(self):
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"
+ errmsg = (r"argument should be a string or a Rational instance or "
+ r"have the as_integer_ratio\(\) method")
# the type also has an "as_integer_ratio" attribute.
self.assertRaisesRegex(TypeError, errmsg, F, Ratio)
# bad ratio
@@ -388,6 +390,8 @@ class B(metaclass=M):
pass
self.assertRaisesRegex(TypeError, errmsg, F, B)
self.assertRaisesRegex(TypeError, errmsg, F, B())
+ self.assertRaises(TypeError, F.from_number, B)
+ self.assertRaises(TypeError, F.from_number, B())
def testFromString(self):
self.assertEqual((5, 1), _components(F("5")))
@@ -594,6 +598,37 @@ def testFromDecimal(self):
ValueError, "cannot convert NaN to integer ratio",
F.from_decimal, Decimal("snan"))
+ def testFromNumber(self, cls=F):
+ def check(arg, numerator, denominator):
+ f = cls.from_number(arg)
+ self.assertIs(type(f), cls)
+ self.assertEqual(f.numerator, numerator)
+ self.assertEqual(f.denominator, denominator)
+
+ check(10, 10, 1)
+ check(2.5, 5, 2)
+ check(Decimal('2.5'), 5, 2)
+ check(F(22, 7), 22, 7)
+ check(DummyFraction(22, 7), 22, 7)
+ check(Rat(22, 7), 22, 7)
+ check(Ratio((22, 7)), 22, 7)
+ self.assertRaises(TypeError, cls.from_number, 3+4j)
+ self.assertRaises(TypeError, cls.from_number, '5/2')
+ self.assertRaises(TypeError, cls.from_number, [])
+ self.assertRaises(OverflowError, cls.from_number, float('inf'))
+ self.assertRaises(OverflowError, cls.from_number, Decimal('inf'))
+
+ # as_integer_ratio not defined in a class
+ class A:
+ pass
+ a = A()
+ a.as_integer_ratio = lambda: (9, 5)
+ check(a, 9, 5)
+
+ def testFromNumber_subclass(self):
+ self.testFromNumber(DummyFraction)
+
+
def test_is_integer(self):
self.assertTrue(F(1, 1).is_integer())
self.assertTrue(F(-1, 1).is_integer())
diff --git
a/Misc/NEWS.d/next/Library/2024-07-15-19-34-56.gh-issue-121797.qDqj59.rst
b/Misc/NEWS.d/next/Library/2024-07-15-19-34-56.gh-issue-121797.qDqj59.rst
new file mode 100644
index 00000000000000..9525379587f6cd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-07-15-19-34-56.gh-issue-121797.qDqj59.rst
@@ -0,0 +1,2 @@
+Add alternative :class:`~fractions.Fraction` constructor
+:meth:`Fraction.from_number() <fractions.Fraction.from_number>`.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]