From 94ee023b111d274ed66342f661d757a40c221d7b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 21 Mar 2026 09:21:31 +0300 Subject: [PATCH] gh-146151: memoryview supports 'F' and 'D' format types (complex) --- Doc/whatsnew/3.15.rst | 5 ++ Lib/test/test_buffer.py | 20 ++++-- Lib/test/test_memoryview.py | 8 +++ ...-03-21-08-11-58.gh-issue-146151.4-lhim.rst | 3 + Modules/_testbuffer.c | 4 +- Objects/memoryobject.c | 65 +++++++++++++++++-- 6 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5689ecbffc4b30..8cf5671b28d2be 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -598,6 +598,11 @@ Other language changes making it a :term:`generic type`. (Contributed by James Hilton-Balfe in :gh:`128335`.) +* The class :class:`memoryview` now supports the :c:expr:`float complex` and + :c:expr:`double complex` C types (formatting characters ``'F'`` and ``'D'`` + respectively). + (Contributed by Sergey B Kirpichev in :gh:`146151`.) + New modules =========== diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index ab65a44bda6e7e..5a4f031e298c2f 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -66,7 +66,8 @@ '?':0, 'c':0, 'b':0, 'B':0, 'h':0, 'H':0, 'i':0, 'I':0, 'l':0, 'L':0, 'n':0, 'N':0, - 'e':0, 'f':0, 'd':0, 'P':0 + 'e':0, 'f':0, 'd':0, 'P':0, + 'F':0, 'D':0 } # NumPy does not have 'n' or 'N': @@ -92,7 +93,9 @@ 'l':(-(1<<31), 1<<31), 'L':(0, 1<<32), 'q':(-(1<<63), 1<<63), 'Q':(0, 1<<64), 'e':(-65519, 65520), 'f':(-(1<<63), 1<<63), - 'd':(-(1<<1023), 1<<1023) + 'd':(-(1<<1023), 1<<1023), + 'F':(-(1<<63), 1<<63), + 'D':(-(1<<1023), 1<<1023) } def native_type_range(fmt): @@ -107,6 +110,10 @@ def native_type_range(fmt): lh = (-(1<<63), 1<<63) elif fmt == 'd': lh = (-(1<<1023), 1<<1023) + elif fmt == 'F': + lh = (-(1<<63), 1<<63) + elif fmt == 'D': + lh = (-(1<<1023), 1<<1023) else: for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7): try: @@ -175,6 +182,11 @@ def randrange_fmt(mode, char, obj): if char in 'efd': x = struct.pack(char, x) x = struct.unpack(char, x)[0] + if char in 'FD': + y = randrange(*fmtdict[mode][char]) + x = complex(x, y) + x = struct.pack(char, x) + x = struct.unpack(char, x)[0] return x def gen_item(fmt, obj): @@ -3015,7 +3027,7 @@ def test_memoryview_assign(self): m = memoryview(nd) self.assertRaises(TypeError, m.__setitem__, 0, 100) - ex = ndarray(list(range(120)), shape=[1,2,3,4,5], flags=ND_WRITABLE) + ex = ndarray(list(range(144)), shape=[1,2,3,4,6], flags=ND_WRITABLE) m1 = memoryview(ex) for fmt, _range in fmtdict['@'].items(): @@ -3025,7 +3037,7 @@ def test_memoryview_assign(self): continue m2 = m1.cast(fmt) lo, hi = _range - if fmt == 'd' or fmt == 'f': + if fmt in "dfDF": lo, hi = -2**1024, 2**1024 if fmt != 'P': # PyLong_AsVoidPtr() accepts negative numbers self.assertRaises(ValueError, m2.__setitem__, 0, lo-1) diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 0f7dc15b8c6f2c..f8ce4e56679df3 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -682,6 +682,14 @@ def test_half_float(self): self.assertEqual(half_view.nbytes * 2, float_view.nbytes) self.assertListEqual(half_view.tolist(), float_view.tolist()) + def test_complex_types(self): + float_complex_data = struct.pack('FFF', 0.0, -1.5j, 1+2j) + double_complex_data = struct.pack('DDD', 0.0, -1.5j, 1+2j) + float_complex_view = memoryview(float_complex_data).cast('F') + double_complex_view = memoryview(double_complex_data).cast('D') + self.assertEqual(float_complex_view.nbytes * 2, double_complex_view.nbytes) + self.assertListEqual(float_complex_view.tolist(), double_complex_view.tolist()) + def test_memoryview_hex(self): # Issue #9951: memoryview.hex() segfaults with non-contiguous buffers. x = b'0' * 200000 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst new file mode 100644 index 00000000000000..89047713189b5d --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-21-08-11-58.gh-issue-146151.4-lhim.rst @@ -0,0 +1,3 @@ +:class:`memoryview` now supports the :c:expr:`float complex` and +:c:expr:`double complex` C types (formatting characters ``'F'`` and ``'D'`` +respectively). Patch by Sergey B Kirpichev. diff --git a/Modules/_testbuffer.c b/Modules/_testbuffer.c index d2e61e9d6acf24..8b6b617aafa427 100644 --- a/Modules/_testbuffer.c +++ b/Modules/_testbuffer.c @@ -351,7 +351,7 @@ pack_from_list(PyObject *obj, PyObject *items, PyObject *format, item = PySequence_Fast_GET_ITEM(items, i); if ((PyBytes_Check(item) || PyLong_Check(item) || - PyFloat_Check(item)) && nmemb == 1) { + PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) { PyTuple_SET_ITEM(args, 2, item); } else if ((PyList_Check(item) || PyTuple_Check(item)) && @@ -433,7 +433,7 @@ pack_single(char *ptr, PyObject *item, const char *fmt, Py_ssize_t itemsize) PyTuple_SET_ITEM(args, 1, zero); if ((PyBytes_Check(item) || PyLong_Check(item) || - PyFloat_Check(item)) && nmemb == 1) { + PyFloat_Check(item) || PyComplex_Check(item)) && nmemb == 1) { PyTuple_SET_ITEM(args, 2, item); } else if ((PyList_Check(item) || PyTuple_Check(item)) && diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 0ad4f02d80bf50..00e7955d15118a 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1216,6 +1216,8 @@ get_native_fmtchar(char *result, const char *fmt) case 'f': size = sizeof(float); break; case 'd': size = sizeof(double); break; case 'e': size = sizeof(float) / 2; break; + case 'F': size = 2*sizeof(float); break; + case 'D': size = 2*sizeof(double); break; case '?': size = sizeof(_Bool); break; case 'P': size = sizeof(void *); break; } @@ -1260,6 +1262,8 @@ get_native_fmtstr(const char *fmt) case 'f': RETURN("f"); case 'd': RETURN("d"); case 'e': RETURN("e"); + case 'F': RETURN("F"); + case 'D': RETURN("D"); case '?': RETURN("?"); case 'P': RETURN("P"); } @@ -1785,7 +1789,7 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt) long long lld; long ld; Py_ssize_t zd; - double d; + double d[2]; unsigned char uc; void *p; @@ -1823,9 +1827,20 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt) case 'N': UNPACK_SINGLE(zu, ptr, size_t); goto convert_zu; /* floats */ - case 'f': UNPACK_SINGLE(d, ptr, float); goto convert_double; - case 'd': UNPACK_SINGLE(d, ptr, double); goto convert_double; - case 'e': d = PyFloat_Unpack2(ptr, endian); goto convert_double; + case 'f': UNPACK_SINGLE(d[0], ptr, float); goto convert_double; + case 'd': UNPACK_SINGLE(d[0], ptr, double); goto convert_double; + case 'e': d[0] = PyFloat_Unpack2(ptr, endian); goto convert_double; + + /* complexes */ + case 'F': + d[0] = PyFloat_Unpack4(ptr, endian); + d[1] = PyFloat_Unpack4(ptr + sizeof(float), endian); + goto convert_double_complex; + + case 'D': + d[0] = PyFloat_Unpack8(ptr, endian); + d[1] = PyFloat_Unpack8(ptr + sizeof(double), endian); + goto convert_double_complex; /* bytes object */ case 'c': goto convert_bytes; @@ -1853,7 +1868,9 @@ unpack_single(PyMemoryViewObject *self, const char *ptr, const char *fmt) convert_zu: return PyLong_FromSize_t(zu); convert_double: - return PyFloat_FromDouble(d); + return PyFloat_FromDouble(d[0]); +convert_double_complex: + return PyComplex_FromDoubles(d[0], d[1]); convert_bool: return PyBool_FromLong(ld); convert_bytes: @@ -1885,6 +1902,7 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt long ld; Py_ssize_t zd; double d; + Py_complex c; void *p; #if PY_LITTLE_ENDIAN @@ -1986,6 +2004,25 @@ pack_single(PyMemoryViewObject *self, char *ptr, PyObject *item, const char *fmt } break; + /* complexes */ + case 'F': case 'D': + c = PyComplex_AsCComplex(item); + if (c.real == -1.0 && PyErr_Occurred()) { + goto err_occurred; + } + CHECK_RELEASED_INT_AGAIN(self); + if (fmt[0] == 'D') { + double x[2] = {c.real, c.imag}; + + memcpy(ptr, &x, sizeof(x)); + } + else { + float x[2] = {(float)c.real, (float)c.imag}; + + memcpy(ptr, &x, sizeof(x)); + } + break; + /* bool */ case '?': ld = PyObject_IsTrue(item); @@ -3023,6 +3060,24 @@ unpack_cmp(const char *p, const char *q, char fmt, return (u == v); } + /* complexes */ + case 'F': + { + float x[2], y[2]; + + memcpy(&x, p, sizeof(x)); + memcpy(&y, q, sizeof(y)); + return (x[0] == y[0]) && (x[1] == y[1]); + } + case 'D': + { + double x[2], y[2]; + + memcpy(&x, p, sizeof(x)); + memcpy(&y, q, sizeof(y)); + return (x[0] == y[0]) && (x[1] == y[1]); + } + /* bytes object */ case 'c': return *p == *q;