From 95b0c7aeb80fe514a061c3ab801376e0fe0fdf32 Mon Sep 17 00:00:00 2001 From: Broyojo Date: Fri, 13 Nov 2026 19:21:42 -0500 Subject: [PATCH 1/2] gh-161968: Fix `x *= seq` when `x` is an instance of a Python class with `__index__` --- Lib/test/test_augassign.py | 32 +++++++++++++++++++ ...11-13-00-00-00.gh-issue-161968.ytuft5z.rst | 3 ++ Objects/abstract.c | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst diff --git a/Lib/test/test_augassign.py b/Lib/test/test_augassign.py index 5930d9e7a29fa2..f25d5bd07b9cef 100644 --- a/Lib/test/test_augassign.py +++ b/Lib/test/test_augassign.py @@ -1,5 +1,7 @@ # Augmented assignment test. +import enum +import operator import unittest @@ -64,6 +66,36 @@ def testSequences(self): self.assertEqual(x, [1, 2, 1, 2, 3]) self.assertTrue(x is y) + def testSequenceRepeatRightOperand(self): + # gh-XXXXXX: "n *= seq" must fall back to seq's repeat, as "n * seq" + # does, when n's type has sequence methods but no repeat slot. + class MyInt(int): + pass + class Index: + def __index__(self): + return 2 + class Number(enum.IntEnum): + TWO = 2 + for n in MyInt(2), Index(), Number.TWO: + for seq in [1], (1,), 'a', b'a', bytearray(b'a'): + with self.subTest(n=n, seq=seq): + expected = n * seq + x = n + x *= seq + self.assertEqual(x, expected) + self.assertEqual(operator.imul(n, seq), expected) + + # The right operand is not modified in place. + seq = [1] + x = MyInt(2) + x *= seq + self.assertEqual(x, [1, 1]) + self.assertEqual(seq, [1]) + + x = Index() + with self.assertRaises(TypeError): + x *= Index() + def testCustomMethods1(self): class aug_test: diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst new file mode 100644 index 00000000000000..735cc31c777e93 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst @@ -0,0 +1,3 @@ +Fix ``x *= seq`` raising :exc:`TypeError` when *x* is an instance of a Python +class with :meth:`~object.__index__`, such as an :class:`int` subclass or an +:class:`enum.IntEnum` member, while ``x * seq`` works. diff --git a/Objects/abstract.c b/Objects/abstract.c index da2a0b0c285b52..6bb1ac572b0c6e 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1344,7 +1344,7 @@ PyNumber_InPlaceMultiply(PyObject *v, PyObject *w) if (f != NULL) return sequence_repeat(f, v, w); } - else if (mw != NULL) { + if (mw != NULL) { /* Note that the right hand operand should not be * mutated in this case so sq_inplace_repeat is not * used. */ From d13d63d3d07c01d6aef1e0bf3aa10ac4bce46f78 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 14 Nov 2026 10:20:32 +0000 Subject: [PATCH 2/2] Minor adjustments --- Lib/test/test_augassign.py | 21 ++++--------------- ...-11-13-00-00-00.gh-issue-161968.ytuf5z.rst | 2 ++ ...11-13-00-00-00.gh-issue-161968.ytuft5z.rst | 3 --- 3 files changed, 6 insertions(+), 20 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuf5z.rst delete mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst diff --git a/Lib/test/test_augassign.py b/Lib/test/test_augassign.py index f25d5bd07b9cef..8cef5a970a5213 100644 --- a/Lib/test/test_augassign.py +++ b/Lib/test/test_augassign.py @@ -1,7 +1,5 @@ # Augmented assignment test. -import enum -import operator import unittest @@ -67,34 +65,23 @@ def testSequences(self): self.assertTrue(x is y) def testSequenceRepeatRightOperand(self): - # gh-XXXXXX: "n *= seq" must fall back to seq's repeat, as "n * seq" - # does, when n's type has sequence methods but no repeat slot. + # gh-161968 class MyInt(int): pass class Index: def __index__(self): return 2 - class Number(enum.IntEnum): - TWO = 2 - for n in MyInt(2), Index(), Number.TWO: - for seq in [1], (1,), 'a', b'a', bytearray(b'a'): - with self.subTest(n=n, seq=seq): - expected = n * seq - x = n - x *= seq - self.assertEqual(x, expected) - self.assertEqual(operator.imul(n, seq), expected) - # The right operand is not modified in place. seq = [1] x = MyInt(2) x *= seq self.assertEqual(x, [1, 1]) + # The right operand is not modified in place. self.assertEqual(seq, [1]) x = Index() - with self.assertRaises(TypeError): - x *= Index() + x *= 'a' + self.assertEqual(x, 'aa') def testCustomMethods1(self): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuf5z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuf5z.rst new file mode 100644 index 00000000000000..12d61b2eeee74e --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuf5z.rst @@ -0,0 +1,2 @@ +Fix ``x *= seq`` raising :exc:`TypeError` when ``x`` is an instance of an +:class:`int` subclass or of a class defining :meth:`~object.__index__`. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst deleted file mode 100644 index 735cc31c777e93..00000000000000 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-13-00-00-00.gh-issue-161968.ytuft5z.rst +++ /dev/null @@ -1,3 +0,0 @@ -Fix ``x *= seq`` raising :exc:`TypeError` when *x* is an instance of a Python -class with :meth:`~object.__index__`, such as an :class:`int` subclass or an -:class:`enum.IntEnum` member, while ``x * seq`` works.