From a5b96d832090af8b90b0362c135e5deeb5ad9c47 Mon Sep 17 00:00:00 2001 From: Eclips4 Date: Mon, 20 Mar 2023 13:25:30 +0200 Subject: [PATCH 1/9] Fix confused traceback --- Lib/fractions.py | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index c95db0730e5b6d..00ebf3b59ad6a7 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -611,29 +611,33 @@ class doesn't subclass a concrete type, there's no """ def forward(a, b): - if isinstance(b, Fraction): - return monomorphic_operator(a, b) - elif isinstance(b, int): - return monomorphic_operator(a, Fraction(b)) - elif isinstance(b, float): - return fallback_operator(float(a), b) - elif isinstance(b, complex): - return fallback_operator(complex(a), b) - else: - return NotImplemented + try: + if isinstance(b, Fraction): + return monomorphic_operator(a, b) + elif isinstance(b, int): + return monomorphic_operator(a, Fraction(b)) + elif isinstance(b, float): + return fallback_operator(float(a), b) + elif isinstance(b, complex): + return fallback_operator(complex(a), b) + except TypeError: + pass + return NotImplemented forward.__name__ = '__' + fallback_operator.__name__ + '__' forward.__doc__ = monomorphic_operator.__doc__ def reverse(b, a): - if isinstance(a, numbers.Rational): - # Includes ints. - return monomorphic_operator(Fraction(a), b) - elif isinstance(a, numbers.Real): - return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): - return fallback_operator(complex(a), complex(b)) - else: - return NotImplemented + try: + if isinstance(a, numbers.Rational): + # Includes ints. + return monomorphic_operator(Fraction(a), b) + elif isinstance(a, numbers.Real): + return fallback_operator(float(a), float(b)) + elif isinstance(a, numbers.Complex): + return fallback_operator(complex(a), complex(b)) + except TypeError: + pass + return NotImplemented reverse.__name__ = '__r' + fallback_operator.__name__ + '__' reverse.__doc__ = monomorphic_operator.__doc__ From 31dca350c8c27ddaf71016ddb8fc869c110c69ca Mon Sep 17 00:00:00 2001 From: Eclips4 Date: Mon, 20 Mar 2023 14:31:39 +0200 Subject: [PATCH 2/9] Add tests --- Lib/test/test_fractions.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index e112f49d2e7944..8ff91d4e21b5af 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1220,6 +1220,29 @@ def test_invalid_formats(self): with self.assertRaises(ValueError): format(fraction, spec) + def test_traceback_in_mod_and_floordiv_with_complex_objects(self): + # See issue-102840 for more details. + + a = F(1, 2) + b = 1j + mod = lambda x, y: x % y + floordiv = lambda x, y: x // y + message = "unsupported operand type(s) for %s: %s and %s" + # test forward + self.assertRaisesMessage(TypeError, + message % ("%", "'Fraction'", "'complex'"), + mod, a, b) + self.assertRaisesMessage(TypeError, + message % ("//", "'Fraction'", "'complex'"), + floordiv, a, b) + # test reverse + self.assertRaisesMessage(TypeError, + message % ("%", "'complex'", "'Fraction'"), + mod, b, a) + self.assertRaisesMessage(TypeError, + message % ("//", "'complex'", "'Fraction'"), + floordiv, b, a) + if __name__ == '__main__': unittest.main() From b03522eda8809bc5a638f6f27ecf7516b2bf6576 Mon Sep 17 00:00:00 2001 From: Eclips4 <80244920+Eclips4@users.noreply.github.com> Date: Mon, 20 Mar 2023 15:58:45 +0300 Subject: [PATCH 3/9] Add sugestion from skirpichev Co-authored-by: Sergey B Kirpichev --- Lib/test/test_fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 8ff91d4e21b5af..c8a4112012241f 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1221,7 +1221,7 @@ def test_invalid_formats(self): format(fraction, spec) def test_traceback_in_mod_and_floordiv_with_complex_objects(self): - # See issue-102840 for more details. + # See issue gh-102840 for more details. a = F(1, 2) b = 1j From 5f878ea426a464df6fb573e6921039bb0f5266bc Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Mon, 1 Jan 2024 22:42:17 +0300 Subject: [PATCH 4/9] Fix indent --- Lib/test/test_fractions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 921291e4e63a09..e2b17ef06255b1 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1290,7 +1290,7 @@ def test_float_format_testfile(self): self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) - def test_traceback_in_mod_and_floordiv_with_complex_objects(self): + def test_traceback_in_mod_and_floordiv_with_complex_objects(self): # See issue gh-102840 for more details. a = F(1, 2) From a8721d12d49f1b0905cc450b04dadceb6bf56bc9 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 10 Feb 2024 14:28:24 +0200 Subject: [PATCH 5/9] Add handle_complex parameter to _operator_fallbacks. Add tests for divmod --- Lib/fractions.py | 52 ++++++++++++++++++-------------------- Lib/test/test_fractions.py | 18 ++++++++----- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index e993a516334bca..748c07074a4581 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -579,7 +579,7 @@ 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=False): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -660,33 +660,29 @@ class doesn't subclass a concrete type, there's no """ def forward(a, b): - try: - if isinstance(b, Fraction): - return monomorphic_operator(a, b) - elif isinstance(b, int): - return monomorphic_operator(a, Fraction(b)) - elif isinstance(b, float): - return fallback_operator(float(a), b) - elif isinstance(b, complex): - return fallback_operator(complex(a), b) - except TypeError: - pass - return NotImplemented + if isinstance(b, Fraction): + return monomorphic_operator(a, b) + elif isinstance(b, int): + return monomorphic_operator(a, Fraction(b)) + elif isinstance(b, float): + return fallback_operator(float(a), b) + elif handle_complex and isinstance(b, complex): + return fallback_operator(complex(a), b) + else: + return NotImplemented forward.__name__ = '__' + fallback_operator.__name__ + '__' forward.__doc__ = monomorphic_operator.__doc__ def reverse(b, a): - try: - if isinstance(a, numbers.Rational): - # Includes ints. - return monomorphic_operator(Fraction(a), b) - elif isinstance(a, numbers.Real): - return fallback_operator(float(a), float(b)) - elif isinstance(a, numbers.Complex): - return fallback_operator(complex(a), complex(b)) - except TypeError: - pass - return NotImplemented + if isinstance(a, numbers.Rational): + # Includes ints. + return monomorphic_operator(Fraction(a), b) + elif isinstance(a, numbers.Real): + return fallback_operator(float(a), float(b)) + elif handle_complex and isinstance(a, numbers.Complex): + return fallback_operator(complex(a), complex(b)) + else: + return NotImplemented reverse.__name__ = '__r' + fallback_operator.__name__ + '__' reverse.__doc__ = monomorphic_operator.__doc__ @@ -774,7 +770,7 @@ def _add(a, b): return Fraction._from_coprime_ints(t, s * db) return Fraction._from_coprime_ints(t // g2, s * (db // g2)) - __add__, __radd__ = _operator_fallbacks(_add, operator.add) + __add__, __radd__ = _operator_fallbacks(_add, operator.add, True) def _sub(a, b): """a - b""" @@ -790,7 +786,7 @@ def _sub(a, b): return Fraction._from_coprime_ints(t, s * db) return Fraction._from_coprime_ints(t // g2, s * (db // g2)) - __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub, True) def _mul(a, b): """a * b""" @@ -806,7 +802,7 @@ def _mul(a, b): da //= g2 return Fraction._from_coprime_ints(na * nb, db * da) - __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul, True) def _div(a, b): """a / b""" @@ -828,7 +824,7 @@ def _div(a, b): n, d = -n, -d return Fraction._from_coprime_ints(n, d) - __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv, True) def _floordiv(a, b): """a // b""" diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index e2b17ef06255b1..5dfec53af85ad7 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1290,28 +1290,32 @@ def test_float_format_testfile(self): self.assertEqual(float(format(f, fmt2)), float(rhs)) self.assertEqual(float(format(-f, fmt2)), float('-' + rhs)) - def test_traceback_in_mod_and_floordiv_with_complex_objects(self): + def test_complex_handling(self): # See issue gh-102840 for more details. a = F(1, 2) b = 1j - mod = lambda x, y: x % y - floordiv = lambda x, y: x // y message = "unsupported operand type(s) for %s: %s and %s" # test forward self.assertRaisesMessage(TypeError, message % ("%", "'Fraction'", "'complex'"), - mod, a, b) + operator.mod, a, b) self.assertRaisesMessage(TypeError, message % ("//", "'Fraction'", "'complex'"), - floordiv, a, b) + operator.floordiv, a, b) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "'Fraction'", "'complex'"), + divmod, a, b) # test reverse self.assertRaisesMessage(TypeError, message % ("%", "'complex'", "'Fraction'"), - mod, b, a) + operator.mod, b, a) self.assertRaisesMessage(TypeError, message % ("//", "'complex'", "'Fraction'"), - floordiv, b, a) + operator.floordiv, b, a) + self.assertRaisesMessage(TypeError, + message % ("divmod()", "'complex'", "'Fraction'"), + divmod, b, a) if __name__ == '__main__': From 2e75353e3a2f03252c541e6fe07bab3506f4cdd7 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 10 Feb 2024 15:22:33 +0200 Subject: [PATCH 6/9] Apply suggestions from Serhiy --- Lib/fractions.py | 3 ++- Lib/test/test_fractions.py | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 748c07074a4581..51db4572070df8 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -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, handle_complex=False): + def _operator_fallbacks(monomorphic_operator, fallback_operator, + handle_complex=False): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. diff --git a/Lib/test/test_fractions.py b/Lib/test/test_fractions.py index 5dfec53af85ad7..56cbc9b1bade24 100644 --- a/Lib/test/test_fractions.py +++ b/Lib/test/test_fractions.py @@ -1295,26 +1295,26 @@ def test_complex_handling(self): a = F(1, 2) b = 1j - message = "unsupported operand type(s) for %s: %s and %s" + message = "unsupported operand type(s) for %s: '%s' and '%s'" # test forward self.assertRaisesMessage(TypeError, - message % ("%", "'Fraction'", "'complex'"), + message % ("%", "Fraction", "complex"), operator.mod, a, b) self.assertRaisesMessage(TypeError, - message % ("//", "'Fraction'", "'complex'"), + message % ("//", "Fraction", "complex"), operator.floordiv, a, b) self.assertRaisesMessage(TypeError, - message % ("divmod()", "'Fraction'", "'complex'"), + message % ("divmod()", "Fraction", "complex"), divmod, a, b) # test reverse self.assertRaisesMessage(TypeError, - message % ("%", "'complex'", "'Fraction'"), + message % ("%", "complex", "Fraction"), operator.mod, b, a) self.assertRaisesMessage(TypeError, - message % ("//", "'complex'", "'Fraction'"), + message % ("//", "complex", "Fraction"), operator.floordiv, b, a) self.assertRaisesMessage(TypeError, - message % ("divmod()", "'complex'", "'Fraction'"), + message % ("divmod()", "complex", "Fraction"), divmod, b, a) From 3013f909f23dd484d8ab0497af30c8440314c394 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 10 Feb 2024 15:31:29 +0200 Subject: [PATCH 7/9] Add a NEWS entry --- .../next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst diff --git a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst new file mode 100644 index 00000000000000..6021f7a8c33fd4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst @@ -0,0 +1,2 @@ +Fix confused traceback when floordiv, mod, or divmod operations happens +between instances of :class:`fractions.Fraction` and :class:`complex` From fc773eb8a81bc567290dec7108ca24e600982e6a Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 10 Feb 2024 15:49:49 +0200 Subject: [PATCH 8/9] Apply suggestion from Sergey --- Lib/fractions.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/fractions.py b/Lib/fractions.py index 51db4572070df8..430eb7b76e678a 100644 --- a/Lib/fractions.py +++ b/Lib/fractions.py @@ -580,7 +580,7 @@ def __format__(self, format_spec, /): ) def _operator_fallbacks(monomorphic_operator, fallback_operator, - handle_complex=False): + handle_complex=True): """Generates forward and reverse operators given a purely-rational operator and a function from the operator module. @@ -771,7 +771,7 @@ def _add(a, b): return Fraction._from_coprime_ints(t, s * db) return Fraction._from_coprime_ints(t // g2, s * (db // g2)) - __add__, __radd__ = _operator_fallbacks(_add, operator.add, True) + __add__, __radd__ = _operator_fallbacks(_add, operator.add) def _sub(a, b): """a - b""" @@ -787,7 +787,7 @@ def _sub(a, b): return Fraction._from_coprime_ints(t, s * db) return Fraction._from_coprime_ints(t // g2, s * (db // g2)) - __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub, True) + __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) def _mul(a, b): """a * b""" @@ -803,7 +803,7 @@ def _mul(a, b): da //= g2 return Fraction._from_coprime_ints(na * nb, db * da) - __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul, True) + __mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul) def _div(a, b): """a / b""" @@ -825,13 +825,13 @@ def _div(a, b): n, d = -n, -d return Fraction._from_coprime_ints(n, d) - __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv, True) + __truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv) 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)""" @@ -839,14 +839,14 @@ def _divmod(a, b): 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 From 489b95d1206a1a638b63b32ec0a6d994379263e7 Mon Sep 17 00:00:00 2001 From: Kirill Podoprigora Date: Sat, 10 Feb 2024 16:15:03 +0200 Subject: [PATCH 9/9] Update Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst Co-authored-by: Serhiy Storchaka --- .../Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst index 6021f7a8c33fd4..52668a9424a976 100644 --- a/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst +++ b/Misc/NEWS.d/next/Library/2024-02-10-15-24-20.gh-issue-102840.4mnDq1.rst @@ -1,2 +1,3 @@ Fix confused traceback when floordiv, mod, or divmod operations happens -between instances of :class:`fractions.Fraction` and :class:`complex` +between instances of :class:`fractions.Fraction` and :class:`complex`. +