Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
bpo-37836: support .as_integer_ratio() in Fraction#15327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -57,25 +57,43 @@ def _gcd(a, b): | ||
| """, re.VERBOSE | re.IGNORECASE) | ||
| def _as_integer_ratio(obj): | ||
| """ | ||
| Return a pair (numerator, denominator) if obj has an ``as_integer_ratio`` | ||
| method or is an instance of ``numbers.Rational``. Return ``NotImplemented`` | ||
| if neither works. | ||
| """ | ||
| try: | ||
| f = obj.as_integer_ratio | ||
| except AttributeError: | ||
| if isinstance(obj, numbers.Rational): | ||
| return (obj.numerator, obj.denominator) | ||
| else: | ||
| return f() | ||
| return NotImplemented | ||
| class Fraction(numbers.Rational): | ||
| """This class implements rational numbers. | ||
| In the two-argument form of the constructor, Fraction(8, 6) will | ||
| produce a rational number equivalent to 4/3. Both arguments must | ||
| be Rational. The numerator defaults to 0 and the denominator | ||
| defaults to 1 so that Fraction(3) == 3 and Fraction() == 0. | ||
| produce a rational number equivalent to 4/3. The numerator defaults | ||
jdemeyer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| to 0 and the denominator defaults to 1 so that Fraction(3) == 3 and | ||
| Fraction() == 0. The numerator and denominator can be: | ||
| - objects with an ``as_integer_ratio()`` method (this includes | ||
| integers, Fractions, floats and Decimal instances) | ||
| Fractions can also be constructed from: | ||
| - other Rational instances | ||
| Fractions can also be constructed from a string (in this case, only | ||
| a single argument is allowed): | ||
| - numeric strings similar to those accepted by the | ||
| float constructor (for example, '-2.3' or '1e10') | ||
| - strings of the form '123/456' | ||
| - float and Decimal instances | ||
| - other Rational instances (including integers) | ||
| """ | ||
| __slots__ = ('_numerator', '_denominator') | ||
| @@ -115,23 +133,19 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True): | ||
| self = super(Fraction, cls).__new__(cls) | ||
| if denominator is None: | ||
| # Fast path for Fraction(int) | ||
| if type(numerator) is int: | ||
| self._numerator = numerator | ||
| self._denominator = 1 | ||
| return self | ||
| elif isinstance(numerator, numbers.Rational): | ||
| self._numerator = numerator.numerator | ||
| self._denominator = numerator.denominator | ||
| return self | ||
| elif isinstance(numerator, (float, Decimal)): | ||
| # Exact conversion | ||
| self._numerator, self._denominator = numerator.as_integer_ratio() | ||
| return self | ||
| nd = _as_integer_ratio(numerator) | ||
| if nd is not NotImplemented: | ||
| numerator, denominator = nd | ||
| # Assume that result is already normalized | ||
| _normalize = False | ||
| # _as_integer_ratio() returned NotImplemented, so we | ||
| # failed unless we got a string | ||
jdemeyer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| elif isinstance(numerator, str): | ||
| # Handle construction from strings. | ||
| m = _RATIONAL_FORMAT.match(numerator) | ||
| if m is None: | ||
| raise ValueError('Invalid literal for Fraction: %r' % | ||
| @@ -158,21 +172,24 @@ def __new__(cls, numerator=0, denominator=None, *, _normalize=True): | ||
| numerator = -numerator | ||
| else: | ||
| raise TypeError("argument should be a string " | ||
| "or a Rational instance") | ||
| raise TypeError("argument should be a string, " | ||
| "a Rational instance or have " | ||
| "an as_integer_ratio() method") | ||
| elif type(numerator) is int is type(denominator): | ||
| pass # *very* normal case | ||
| elif (isinstance(numerator, numbers.Rational) and | ||
| isinstance(denominator, numbers.Rational)): | ||
| numerator, denominator = ( | ||
| numerator.numerator * denominator.denominator, | ||
| denominator.numerator * numerator.denominator | ||
| ) | ||
| # Fast path for Fraction(int, int) | ||
| pass | ||
| else: | ||
| raise TypeError("both arguments should be " | ||
| "Rational instances") | ||
| x = _as_integer_ratio(numerator) | ||
| y = _as_integer_ratio(denominator) | ||
| if x is NotImplemented or y is NotImplemented: | ||
| raise TypeError("both arguments should be " | ||
| "a Rational instance or have " | ||
| "an as_integer_ratio() method") | ||
| num1, den1 = x | ||
| den2, num2 = y | ||
| numerator = num1 * num2 | ||
| denominator = den1 * den2 | ||
| if denominator == 0: | ||
| raise ZeroDivisionError('Fraction(%s, 0)' % numerator) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -78,6 +78,15 @@ def __ge__(self, other): | ||
| def __float__(self): | ||
| assert False, "__float__ should not be invoked" | ||
| class IntegerRatio: | ||
| def __init__(self, num, den): | ||
| self.integer_ratio = (num, den) | ||
| def as_integer_ratio(self): | ||
| return self.integer_ratio | ||
| class DummyFraction(fractions.Fraction): | ||
| """Dummy Fraction subclass for copy and deepcopy testing.""" | ||
| @@ -155,11 +164,13 @@ def testInit(self): | ||
| self.assertRaises(TypeError, F, "3/2", 3) | ||
| self.assertRaises(TypeError, F, 3, 0j) | ||
| self.assertRaises(TypeError, F, 3, 1j) | ||
| self.assertRaises(TypeError, F, 1j, 3) | ||
jdemeyer marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| self.assertRaises(TypeError, F, 1, 2, 3) | ||
| @requires_IEEE_754 | ||
| def testInitFromFloat(self): | ||
| self.assertEqual((5, 2), _components(F(2.5))) | ||
| self.assertEqual((5, 7), _components(F(2.5, 3.5))) | ||
| self.assertEqual((0, 1), _components(F(-0.0))) | ||
| self.assertEqual((3602879701896397, 36028797018963968), | ||
| _components(F(0.1))) | ||
| @@ -171,6 +182,8 @@ def testInitFromFloat(self): | ||
| def testInitFromDecimal(self): | ||
| self.assertEqual((11, 10), | ||
| _components(F(Decimal('1.1')))) | ||
| self.assertEqual((11, 10), | ||
| _components(F(Decimal('9.9'), Decimal(9)))) | ||
| self.assertEqual((7, 200), | ||
| _components(F(Decimal('3.5e-2')))) | ||
| self.assertEqual((0, 1), | ||
| @@ -302,6 +315,14 @@ def testFromDecimal(self): | ||
| ValueError, "cannot convert NaN to integer ratio", | ||
| F.from_decimal, Decimal("snan")) | ||
| def testFromIntegerRatio(self): | ||
| a = IntegerRatio(-5, 4) | ||
| b = IntegerRatio(-5, 3) | ||
| self.assertEqual(F(a).as_integer_ratio(), (-5, 4)) | ||
| self.assertEqual(F(a, b).as_integer_ratio(), (3, 4)) | ||
| self.assertEqual(F(1, a).as_integer_ratio(), (-4, 5)) | ||
| self.assertEqual(F(a, 5.0).as_integer_ratio(), (-1, 4)) | ||
| def test_as_integer_ratio(self): | ||
| self.assertEqual(F(4, 6).as_integer_ratio(), (2, 3)) | ||
| self.assertEqual(F(-4, 6).as_integer_ratio(), (-2, 3)) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| When constructing a :class:`fractions.Fraction`, the given numerator and | ||
| denominator may now be any object with an ``as_integer_ratio`` method. | ||
| This allows, for example, passing floats for both the numerator and denominator. |
Uh oh!
There was an error while loading. Please reload this page.