Skip to content

Commit

Permalink
bpo-44547: Make Fractions objects instances of typing.SupportsInt (GH…
Browse files Browse the repository at this point in the history
…-27851)

Co-authored-by: Łukasz Langa <lukasz@langa.pl>
  • Loading branch information
mdickinson and ambv committed Oct 21, 2021
1 parent 5137538 commit d1b2477
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 3 deletions.
4 changes: 4 additions & 0 deletions Doc/library/fractions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 6 additions & 2 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion Lib/fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,8 +594,15 @@ def __abs__(a):
"""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:
Expand Down
42 changes: 42 additions & 0 deletions Lib/test/test_fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import fractions
import functools
import sys
import typing
import unittest
from copy import copy, deepcopy
import pickle
Expand Down Expand Up @@ -385,6 +386,47 @@ def testConversions(self):

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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement ``Fraction.__int__``, so that a :class:`fractions.Fraction`
instance ``f`` passes an ``isinstance(f, typing.SupportsInt)`` check.

0 comments on commit d1b2477

Please sign in to comment.