https://github.com/python/cpython/commit/c8d2630995fc234f8276e35643a4a43e62224510
commit: c8d2630995fc234f8276e35643a4a43e62224510
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-07-19T08:06:53+03:00
summary:
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.
files:
A Misc/NEWS.d/next/Library/2024-06-08-17-41-11.gh-issue-82017.WpSTGi.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 552d6030b1ceda..410b176c5d5084 100644
--- a/Doc/library/fractions.rst
+++ b/Doc/library/fractions.rst
@@ -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.
diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst
index 8f7b6ebd0af316..777faafe59b4f5 100644
--- a/Doc/whatsnew/3.14.rst
+++ b/Doc/whatsnew/3.14.rst
@@ -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
--
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 565503911bbe97..34fd0803d1b1ab 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -3,7 +3,6 @@
"""Fraction, infinite-precision, rational numbers."""
-from decimal import Decimal
import functools
import math
import numbers
@@ -244,7 +243,9 @@ def __new__(cls, numerator=0, denominator=None):
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 @@ def __new__(cls, numerator=0, denominator=None):
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
diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py
index 589669298e22e2..12c42126301265 100644
--- a/Lib/test/test_fractions.py
+++ b/Lib/test/test_fractions.py
@@ -354,6 +354,41 @@ def testInitFromDecimal(self):
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")))
diff --git
a/Misc/NEWS.d/next/Library/2024-06-08-17-41-11.gh-issue-82017.WpSTGi.rst
b/Misc/NEWS.d/next/Library/2024-06-08-17-41-11.gh-issue-82017.WpSTGi.rst
new file mode 100644
index 00000000000000..7decee7ff3384e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-06-08-17-41-11.gh-issue-82017.WpSTGi.rst
@@ -0,0 +1,2 @@
+Added support for converting any objects that have the
+:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
_______________________________________________
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]