]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-44547: Make Fractions objects instances of typing.SupportsInt (GH-27851)
authorMark Dickinson <mdickinson@enthought.com>
Thu, 21 Oct 2021 22:09:47 +0000 (23:09 +0100)
committerGitHub <noreply@github.com>
Thu, 21 Oct 2021 22:09:47 +0000 (00:09 +0200)
Co-authored-by: Ɓukasz Langa <lukasz@langa.pl>
Doc/library/fractions.rst
Doc/whatsnew/3.11.rst
Lib/fractions.py
Lib/test/test_fractions.py
Misc/NEWS.d/next/Library/2021-08-20-10-52-40.bpo-44547.eu0iJq.rst [new file with mode: 0644]

index d04de8f8e95a61a99f3d2925cd3c9811248ccbde..c893f2d5389d5793f185fc3a58b81d2f2d0eab81 100644 (file)
@@ -94,6 +94,10 @@ another rational number, or from a string.
       Underscores are now permitted when creating a :class:`Fraction` instance
       from a string, following :PEP:`515` rules.
 
+   .. versionchanged:: 3.11
+      :class:`Fraction` implements ``__int__`` now to satisfy
+      ``typing.SupportsInt`` instance checks.
+
    .. attribute:: numerator
 
       Numerator of the Fraction in lowest term.
index 74fc7536ea231cfc0fb1308203fc3d9ab23252d5..a03fff8a9e10aef3c6382cd7d8d78e462ea492b8 100644 (file)
@@ -193,8 +193,12 @@ Improved Modules
 fractions
 ---------
 
-Support :PEP:`515`-style initialization of :class:`~fractions.Fraction` from
-string.  (Contributed by Sergey B Kirpichev in :issue:`44258`.)
+* Support :PEP:`515`-style initialization of :class:`~fractions.Fraction` from
+  string.  (Contributed by Sergey B Kirpichev in :issue:`44258`.)
+
+* :class:`~fractions.Fraction` now implements an ``__int__`` method, so
+  that an ``isinstance(some_fraction, typing.SupportsInt)`` check passes.
+  (Contributed by Mark Dickinson in :issue:`44547`.)
 
 
 math
index 180cd94c2879cc6a536c09e94095224296e0d284..f9ac882ec002fa4939948f0fd8c519e2963b4970 100644 (file)
@@ -594,8 +594,15 @@ class Fraction(numbers.Rational):
         """abs(a)"""
         return Fraction(abs(a._numerator), a._denominator, _normalize=False)
 
+    def __int__(a, _index=operator.index):
+        """int(a)"""
+        if a._numerator < 0:
+            return _index(-(-a._numerator // a._denominator))
+        else:
+            return _index(a._numerator // a._denominator)
+
     def __trunc__(a):
-        """trunc(a)"""
+        """math.trunc(a)"""
         if a._numerator < 0:
             return -(-a._numerator // a._denominator)
         else:
index bbf7709fe959bef50024cf051e9a6dd87ac29a39..fc46e8674fc46e971017b03cb41d376cd2b4f748 100644 (file)
@@ -8,6 +8,7 @@ import operator
 import fractions
 import functools
 import sys
+import typing
 import unittest
 from copy import copy, deepcopy
 import pickle
@@ -385,6 +386,47 @@ class FractionTest(unittest.TestCase):
 
         self.assertTypedEquals(0.1+0j, complex(F(1,10)))
 
+    def testSupportsInt(self):
+        # See bpo-44547.
+        f = F(3, 2)
+        self.assertIsInstance(f, typing.SupportsInt)
+        self.assertEqual(int(f), 1)
+        self.assertEqual(type(int(f)), int)
+
+    def testIntGuaranteesIntReturn(self):
+        # Check that int(some_fraction) gives a result of exact type `int`
+        # even if the fraction is using some other Integral type for its
+        # numerator and denominator.
+
+        class CustomInt(int):
+            """
+            Subclass of int with just enough machinery to convince the Fraction
+            constructor to produce something with CustomInt numerator and
+            denominator.
+            """
+
+            @property
+            def numerator(self):
+                return self
+
+            @property
+            def denominator(self):
+                return CustomInt(1)
+
+            def __mul__(self, other):
+                return CustomInt(int(self) * int(other))
+
+            def __floordiv__(self, other):
+                return CustomInt(int(self) // int(other))
+
+        f = F(CustomInt(13), CustomInt(5))
+
+        self.assertIsInstance(f.numerator, CustomInt)
+        self.assertIsInstance(f.denominator, CustomInt)
+        self.assertIsInstance(f, typing.SupportsInt)
+        self.assertEqual(int(f), 2)
+        self.assertEqual(type(int(f)), int)
+
     def testBoolGuarateesBoolReturn(self):
         # Ensure that __bool__ is used on numerator which guarantees a bool
         # return.  See also bpo-39274.
diff --git a/Misc/NEWS.d/next/Library/2021-08-20-10-52-40.bpo-44547.eu0iJq.rst b/Misc/NEWS.d/next/Library/2021-08-20-10-52-40.bpo-44547.eu0iJq.rst
new file mode 100644 (file)
index 0000000..a5f425e
--- /dev/null
@@ -0,0 +1,2 @@
+Implement ``Fraction.__int__``, so that a :class:`fractions.Fraction`
+instance ``f`` passes an ``isinstance(f, typing.SupportsInt)`` check.