Skip to content

Commit

Permalink
pythongh-102840: Fix confused traceback when floordiv or mod operatio…
Browse files Browse the repository at this point in the history
…ns happens between Fraction and complex objects (pythonGH-102842)
  • Loading branch information
Eclips4 authored and fsc-eriker committed Feb 14, 2024
1 parent 7a5127a commit bec39e4
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 6 deletions.
13 changes: 7 additions & 6 deletions Lib/fractions.py
Expand Up @@ -579,7 +579,8 @@ def __format__(self, format_spec, /):
f"for object of type {type(self).__name__!r}"
)

def _operator_fallbacks(monomorphic_operator, fallback_operator):
def _operator_fallbacks(monomorphic_operator, fallback_operator,
handle_complex=True):
"""Generates forward and reverse operators given a purely-rational
operator and a function from the operator module.
Expand Down Expand Up @@ -666,7 +667,7 @@ def forward(a, b):
return monomorphic_operator(a, Fraction(b))
elif isinstance(b, float):
return fallback_operator(float(a), b)
elif isinstance(b, complex):
elif handle_complex and isinstance(b, complex):
return fallback_operator(complex(a), b)
else:
return NotImplemented
Expand All @@ -679,7 +680,7 @@ def reverse(b, a):
return monomorphic_operator(Fraction(a), b)
elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.Complex):
elif handle_complex and isinstance(a, numbers.Complex):
return fallback_operator(complex(a), complex(b))
else:
return NotImplemented
Expand Down Expand Up @@ -830,22 +831,22 @@ def _floordiv(a, b):
"""a // b"""
return (a.numerator * b.denominator) // (a.denominator * b.numerator)

__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv, False)

def _divmod(a, b):
"""(a // b, a % b)"""
da, db = a.denominator, b.denominator
div, n_mod = divmod(a.numerator * db, da * b.numerator)
return div, Fraction(n_mod, da * db)

__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod, False)

def _mod(a, b):
"""a % b"""
da, db = a.denominator, b.denominator
return Fraction((a.numerator * db) % (b.numerator * da), da * db)

__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False)

def __pow__(a, b):
"""a ** b
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_fractions.py
Expand Up @@ -1314,6 +1314,33 @@ def test_float_format_testfile(self):
self.assertEqual(float(format(f, fmt2)), float(rhs))
self.assertEqual(float(format(-f, fmt2)), float('-' + rhs))

def test_complex_handling(self):
# See issue gh-102840 for more details.

a = F(1, 2)
b = 1j
message = "unsupported operand type(s) for %s: '%s' and '%s'"
# test forward
self.assertRaisesMessage(TypeError,
message % ("%", "Fraction", "complex"),
operator.mod, a, b)
self.assertRaisesMessage(TypeError,
message % ("//", "Fraction", "complex"),
operator.floordiv, a, b)
self.assertRaisesMessage(TypeError,
message % ("divmod()", "Fraction", "complex"),
divmod, a, b)
# test reverse
self.assertRaisesMessage(TypeError,
message % ("%", "complex", "Fraction"),
operator.mod, b, a)
self.assertRaisesMessage(TypeError,
message % ("//", "complex", "Fraction"),
operator.floordiv, b, a)
self.assertRaisesMessage(TypeError,
message % ("divmod()", "complex", "Fraction"),
divmod, b, a)


if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,3 @@
Fix confused traceback when floordiv, mod, or divmod operations happens
between instances of :class:`fractions.Fraction` and :class:`complex`.

0 comments on commit bec39e4

Please sign in to comment.