diff --git a/Doc/library/fractions.rst b/Doc/library/fractions.rst index d04de8f8e95..c893f2d5389 100644 --- a/Doc/library/fractions.rst +++ b/Doc/library/fractions.rst @@ -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. diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index 74fc7536ea2..a03fff8a9e1 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -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 diff --git a/Lib/fractions.py b/Lib/fractions.py index 180cd94c287..f9ac882ec00 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -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: diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index bbf7709fe95..fc46e8674fc 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -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 index 00000000000..a5f425e17ca --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-08-20-10-52-40.bpo-44547.eu0iJq.rst @@ -0,0 +1,2 @@ +Implement ``Fraction.__int__``, so that a :class:`fractions.Fraction` +instance ``f`` passes an ``isinstance(f, typing.SupportsInt)`` check.