Skip to content

Commit d1b2477

Browse files
mdickinsonambv
andauthored
bpo-44547: Make Fractions objects instances of typing.SupportsInt (GH-27851)
Co-authored-by: Łukasz Langa <[email protected]>
1 parent 5137538 commit d1b2477

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

Doc/library/fractions.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ another rational number, or from a string.
9494
Underscores are now permitted when creating a :class:`Fraction` instance
9595
from a string, following :PEP:`515` rules.
9696

97+
.. versionchanged:: 3.11
98+
:class:`Fraction` implements ``__int__`` now to satisfy
99+
``typing.SupportsInt`` instance checks.
100+
97101
.. attribute:: numerator
98102

99103
Numerator of the Fraction in lowest term.

Doc/whatsnew/3.11.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,12 @@ Improved Modules
193193
fractions
194194
---------
195195

196-
Support :PEP:`515`-style initialization of :class:`~fractions.Fraction` from
197-
string. (Contributed by Sergey B Kirpichev in :issue:`44258`.)
196+
* Support :PEP:`515`-style initialization of :class:`~fractions.Fraction` from
197+
string. (Contributed by Sergey B Kirpichev in :issue:`44258`.)
198+
199+
* :class:`~fractions.Fraction` now implements an ``__int__`` method, so
200+
that an ``isinstance(some_fraction, typing.SupportsInt)`` check passes.
201+
(Contributed by Mark Dickinson in :issue:`44547`.)
198202

199203

200204
math

Lib/fractions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -594,8 +594,15 @@ def __abs__(a):
594594
"""abs(a)"""
595595
return Fraction(abs(a._numerator), a._denominator, _normalize=False)
596596

597+
def __int__(a, _index=operator.index):
598+
"""int(a)"""
599+
if a._numerator < 0:
600+
return _index(-(-a._numerator // a._denominator))
601+
else:
602+
return _index(a._numerator // a._denominator)
603+
597604
def __trunc__(a):
598-
"""trunc(a)"""
605+
"""math.trunc(a)"""
599606
if a._numerator < 0:
600607
return -(-a._numerator // a._denominator)
601608
else:

Lib/test/test_fractions.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import fractions
99
import functools
1010
import sys
11+
import typing
1112
import unittest
1213
from copy import copy, deepcopy
1314
import pickle
@@ -385,6 +386,47 @@ def testConversions(self):
385386

386387
self.assertTypedEquals(0.1+0j, complex(F(1,10)))
387388

389+
def testSupportsInt(self):
390+
# See bpo-44547.
391+
f = F(3, 2)
392+
self.assertIsInstance(f, typing.SupportsInt)
393+
self.assertEqual(int(f), 1)
394+
self.assertEqual(type(int(f)), int)
395+
396+
def testIntGuaranteesIntReturn(self):
397+
# Check that int(some_fraction) gives a result of exact type `int`
398+
# even if the fraction is using some other Integral type for its
399+
# numerator and denominator.
400+
401+
class CustomInt(int):
402+
"""
403+
Subclass of int with just enough machinery to convince the Fraction
404+
constructor to produce something with CustomInt numerator and
405+
denominator.
406+
"""
407+
408+
@property
409+
def numerator(self):
410+
return self
411+
412+
@property
413+
def denominator(self):
414+
return CustomInt(1)
415+
416+
def __mul__(self, other):
417+
return CustomInt(int(self) * int(other))
418+
419+
def __floordiv__(self, other):
420+
return CustomInt(int(self) // int(other))
421+
422+
f = F(CustomInt(13), CustomInt(5))
423+
424+
self.assertIsInstance(f.numerator, CustomInt)
425+
self.assertIsInstance(f.denominator, CustomInt)
426+
self.assertIsInstance(f, typing.SupportsInt)
427+
self.assertEqual(int(f), 2)
428+
self.assertEqual(type(int(f)), int)
429+
388430
def testBoolGuarateesBoolReturn(self):
389431
# Ensure that __bool__ is used on numerator which guarantees a bool
390432
# return. See also bpo-39274.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement ``Fraction.__int__``, so that a :class:`fractions.Fraction`
2+
instance ``f`` passes an ``isinstance(f, typing.SupportsInt)`` check.

0 commit comments

Comments
 (0)