Skip to content

Commit 20d1237

Browse files
committed
Generally use .as_integer_ratio() in the constructor if available.
See python/cpython#120271
1 parent 91c4efc commit 20d1237

File tree

3 files changed

+50
-5
lines changed

3 files changed

+50
-5
lines changed

CHANGES.rst

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
ChangeLog
22
=========
33

4+
1.19 (2024-??-??)
5+
-----------------
6+
7+
* Generally use ``.as_integer_ratio()`` in the constructor if available.
8+
https://github.com/python/cpython/pull/120271
9+
10+
411
1.18 (2024-04-03)
512
-----------------
613

src/quicktions.pyx

+4-4
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ cdef cunumber _binary_gcd(cunumber a, cunumber b):
197197
return b
198198
if not b:
199199
return a
200-
200+
201201
cdef int i = trailing_zeros(a)
202202
a >>= i
203203
cdef int j = trailing_zeros(b)
@@ -471,7 +471,8 @@ cdef class Fraction:
471471
_normalize = False
472472
# fall through to normalisation below
473473

474-
elif isinstance(numerator, float):
474+
elif isinstance(numerator, float) or (
475+
not isinstance(numerator, type) and hasattr(numerator, 'as_integer_ratio')):
475476
# Exact conversion
476477
self._numerator, self._denominator = numerator.as_integer_ratio()
477478
return
@@ -492,8 +493,7 @@ cdef class Fraction:
492493
return
493494

494495
else:
495-
raise TypeError("argument should be a string "
496-
"or a Rational instance")
496+
raise TypeError("argument should be a string or a number")
497497

498498
elif type(numerator) is int is type(denominator):
499499
pass # *very* normal case

src/test_fractions.py

+39-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
44
# 2011, 2012, 2013, 2014 Python Software Foundation; All Rights Reserved
55
#
6-
# Based on the "test/test_fractions" module in CPython 3.4.
6+
# Originally based on the "test/test_fractions" module in CPython 3.4.
77
# https://hg.python.org/cpython/file/b18288f24501/Lib/test/test_fractions.py
8+
#
9+
# Updated to match the recent development in CPython.
10+
# https://github.com/python/cpython/blob/main/Lib/test/test_fractions.py
811

912
"""Tests for Lib/fractions.py, slightly adapted for quicktions."""
1013

@@ -279,6 +282,41 @@ def testInitFromDecimal(self):
279282
self.assertRaises(OverflowError, F, Decimal('inf'))
280283
self.assertRaises(OverflowError, F, Decimal('-inf'))
281284

285+
def testInitFromIntegerRatio(self):
286+
class Ratio:
287+
def __init__(self, ratio):
288+
self._ratio = ratio
289+
def as_integer_ratio(self):
290+
return self._ratio
291+
292+
self.assertEqual((7, 3), _components(F(Ratio((7, 3)))))
293+
errmsg = "argument should be a string or a number"
294+
# the type also has an "as_integer_ratio" attribute.
295+
self.assertRaisesRegex(TypeError, errmsg, F, Ratio)
296+
# bad ratio
297+
self.assertRaises(TypeError, F, Ratio(7))
298+
self.assertRaises(ValueError, F, Ratio((7,)))
299+
self.assertRaises(ValueError, F, Ratio((7, 3, 1)))
300+
# only single-argument form
301+
self.assertRaises(TypeError, F, Ratio((3, 7)), 11)
302+
self.assertRaises(TypeError, F, 2, Ratio((-10, 9)))
303+
304+
# as_integer_ratio not defined in a class
305+
class A:
306+
pass
307+
a = A()
308+
a.as_integer_ratio = lambda: (9, 5)
309+
self.assertEqual((9, 5), _components(F(a)))
310+
311+
# as_integer_ratio defined in a metaclass
312+
class M(type):
313+
def as_integer_ratio(self):
314+
return (11, 9)
315+
class B(metaclass=M):
316+
pass
317+
self.assertRaisesRegex(TypeError, errmsg, F, B)
318+
self.assertRaisesRegex(TypeError, errmsg, F, B())
319+
282320
def testFromString(self):
283321
self.assertEqual((5, 1), _components(F("5")))
284322
self.assertEqual((3, 2), _components(F("3/2")))

0 commit comments

Comments
 (0)