diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 656318668e6d6e..3cabb2724a8903 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -575,6 +575,59 @@ def test_array_assign(self): m[:] = new_a self.assertEqual(a, new_a) + def test_compare_equal(self): + # A memoryview is equal to itself: there is no need to compare + # individual values. This is not true for float values since they can + # be NaN, and NaN is not equal to itself. + + def check_equal(view, is_equal): + self.assertEqual(view == view, is_equal) + self.assertEqual(view != view, not is_equal) + + # Comparison with a different memoryview doesn't use + # the optimization and should give the same result. + view2 = memoryview(view) + self.assertEqual(view2 == view, is_equal) + self.assertEqual(view2 != view2, not is_equal) + + # Test integer formats + for int_format in 'bBhHiIlLqQ': + with self.subTest(format=int_format): + a = array.array(int_format, [1, 2, 3]) + m = memoryview(a) + check_equal(m, True) + + if int_format in 'bB': + m2 = m.cast('@' + m.format) + check_equal(m2, True) + + # Test '?' format + m = memoryview(b'\0\1\2').cast('?') + check_equal(m, True) + + # Test 'P' format + if struct.calcsize('L') == struct.calcsize('P'): + int_format = 'L' + elif struct.calcsize('Q') == struct.calcsize('P'): + int_format = 'Q' + else: + raise ValueError('unable to get void* format in struct') + a = array.array(int_format, [1, 2, 3]) + m = memoryview(a.tobytes()).cast('P') + check_equal(m, True) + + # Test float formats + for float_format in 'fd': + with self.subTest(format=float_format): + a = array.array(float_format, [1.0, 2.0, float('nan')]) + m = memoryview(a) + # nan is not equal to nan + check_equal(m, False) + + a = array.array(float_format, [1.0, 2.0, 3.0]) + m = memoryview(a) + check_equal(m, True) + class BytesMemorySliceTest(unittest.TestCase, BaseMemorySliceTests, BaseBytesMemoryTests): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst new file mode 100644 index 00000000000000..83d84b9505c5a5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-19-12-49-15.gh-issue-144995.Ob2oYJ.rst @@ -0,0 +1,2 @@ +Optimize :class:`memoryview` comparison: a :class:`memoryview` is equal to +itself, there is no need to compare values. Patch by Victor Stinner. diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index f3b7e4a396b4a1..cbff6daafdceaf 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -3122,6 +3122,33 @@ memory_richcompare(PyObject *v, PyObject *w, int op) } vv = VIEW_ADDR(v); + // For formats supported by the struct module a memoryview is equal to + // itself: there is no need to compare individual values. + // This is not true for float values since they can be NaN, and NaN + // is not equal to itself. So only use this optimization on format known to + // not use floats. + if (v == w) { + int can_compare_ptr; + const char *format = vv->format; + if (format != NULL) { + if (*format == '@') { + format++; + } + // Include only formats known by struct, exclude formats + // "d" (double), "f" (float) and "e" (16-bit float). + can_compare_ptr = (format[0] != 0 + && strchr("bBchHiIlLnNPqQ?", format[0]) != NULL + && format[1] == 0); + } + else { + can_compare_ptr = 1; + } + if (can_compare_ptr) { + equal = 1; + goto result; + } + } + if (PyMemoryView_Check(w)) { if (BASE_INACCESSIBLE(w)) { equal = (v == w);