Skip to content

Commit 6e6f905

Browse files
authored
gh-148675: Add Zd/Zf formats to array, ctypes, memoryview, struct (#148676)
* Add Zd/Zf format support to array, memoryview and struct. * ctypes: Replace F/D/G complex format with Zf/Zd/Zg. * Modify array, ctypes and struct modules to support format strings longer than 1 character (such as "Zd"). * Change array.typecodes type from str to tuple.
1 parent 72f29dc commit 6e6f905

22 files changed

Lines changed: 625 additions & 334 deletions

Doc/library/array.rst

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ defined:
5252
+-----------+--------------------+-------------------+-----------------------+-------+
5353
| ``'D'`` | double complex | complex | 16 | \(4) |
5454
+-----------+--------------------+-------------------+-----------------------+-------+
55+
| ``'Zf'`` | float complex | complex | 8 | \(4) |
56+
+-----------+--------------------+-------------------+-----------------------+-------+
57+
| ``'Zd'`` | double complex | complex | 16 | \(4) |
58+
+-----------+--------------------+-------------------+-----------------------+-------+
5559

5660

5761
Notes:
@@ -80,7 +84,7 @@ Notes:
8084
.. versionadded:: 3.15
8185

8286
(4)
83-
Complex types (``F`` and ``D``) are available unconditionally,
87+
Complex types (``F``, ``D``, ``Zf`` and ``Zd``) are available unconditionally,
8488
regardless on support for complex types (the Annex G of the C11 standard)
8589
by the C compiler.
8690
As specified in the C11 standard, each complex type is represented by a
@@ -105,7 +109,10 @@ The module defines the following item:
105109

106110
.. data:: typecodes
107111

108-
A string with all available type codes.
112+
A tuple with all available type codes.
113+
114+
.. versionchanged:: next
115+
The type changed from :class:`str` to :class:`tuple`.
109116

110117

111118
The module defines the following type:

Doc/library/ctypes.rst

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,19 @@ in both C and ``libffi``, the following complex types are available:
370370
* - :class:`c_float_complex`
371371
- :c:expr:`float complex`
372372
- :py:class:`complex`
373-
- ``'F'``
373+
- ``'Zf'``
374374
* - :class:`c_double_complex`
375375
- :c:expr:`double complex`
376376
- :py:class:`complex`
377-
- ``'D'``
377+
- ``'Zd'``
378378
* - :class:`c_longdouble_complex`
379379
- :c:expr:`long double complex`
380380
- :py:class:`complex`
381-
- ``'G'``
381+
- ``'Zg'``
382+
383+
.. versionchanged:: next
384+
The :py:attr:`~_SimpleCData._type_` types ``F``, ``D`` and ``G`` have been
385+
replaced with ``Zf``, ``Zd`` and ``Zg``.
382386

383387

384388
All these types can be created by calling them with an optional initializer of

Doc/library/struct.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,10 @@ platform-dependent.
264264
+--------+--------------------------+--------------------+----------------+------------+
265265
| ``D`` | :c:expr:`double complex` | complex | 16 | \(10) |
266266
+--------+--------------------------+--------------------+----------------+------------+
267+
| ``Zf`` | :c:expr:`float complex` | complex | 8 | \(10) |
268+
+--------+--------------------------+--------------------+----------------+------------+
269+
| ``Zd`` | :c:expr:`double complex` | complex | 16 | \(10) |
270+
+--------+--------------------------+--------------------+----------------+------------+
267271
| ``s`` | :c:expr:`char[]` | bytes | | \(9) |
268272
+--------+--------------------------+--------------------+----------------+------------+
269273
| ``p`` | :c:expr:`char[]` | bytes | | \(8) |
@@ -280,6 +284,9 @@ platform-dependent.
280284
.. versionchanged:: 3.14
281285
Added support for the ``'F'`` and ``'D'`` formats.
282286

287+
.. versionchanged:: next
288+
Added support for the ``'Zf'`` and ``'Zd'`` formats.
289+
283290
.. seealso::
284291

285292
The :mod:`array` and :ref:`ctypes <ctypes-fundamental-data-types>` modules,
@@ -372,7 +379,7 @@ Notes:
372379
For the ``'F'`` and ``'D'`` format characters, the packed representation uses
373380
the IEEE 754 binary32 and binary64 format for components of the complex
374381
number, regardless of the floating-point format used by the platform.
375-
Note that complex types (``F`` and ``D``) are available unconditionally,
382+
Note that complex types (``F``/``Zf`` and ``D``/``Zd``) are available unconditionally,
376383
despite complex types being an optional feature in C.
377384
As specified in the C11 standard, each complex type is represented by a
378385
two-element C array containing, respectively, the real and imaginary parts.

Doc/whatsnew/3.15.rst

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -671,9 +671,9 @@ Other language changes
671671
(Contributed by James Hilton-Balfe in :gh:`128335`.)
672672

673673
* The class :class:`memoryview` now supports the :c:expr:`float complex` and
674-
:c:expr:`double complex` C types: formatting characters ``'F'`` and ``'D'``
675-
respectively.
676-
(Contributed by Sergey B Kirpichev in :gh:`146151`.)
674+
:c:expr:`double complex` C types: formatting characters ``'F'``/``'Zf'``
675+
and ``'D'``/``'Zd'`` respectively.
676+
(Contributed by Victor Stinner in :gh:`146151` and :gh:`148675`.)
677677

678678
* Allow the *count* argument of :meth:`bytes.replace` to be a keyword.
679679
(Contributed by Stan Ulbrych in :gh:`147856`.)
@@ -724,13 +724,17 @@ array
724724
-----
725725

726726
* Support the :c:expr:`float complex` and :c:expr:`double complex` C types:
727-
formatting characters ``'F'`` and ``'D'`` respectively.
728-
(Contributed by Sergey B Kirpichev in :gh:`146151`.)
727+
formatting characters ``'F'``/``'Zf'`` and ``'D'``/``'Zd'`` respectively.
728+
(Contributed by Victor Stinner in :gh:`146151` and :gh:`148675`.)
729729

730730
* Support half-floats (16-bit IEEE 754 binary interchange format): formatting
731731
character ``'e'``.
732732
(Contributed by Sergey B Kirpichev in :gh:`146238`.)
733733

734+
* The :data:`array.typecodes` type changed from :class:`str` to :class:`tuple`
735+
to support type codes longer than 1 character (``Zf`` and ``Zd``).
736+
(Contributed by Victor Stinner in :gh:`148675`.)
737+
734738

735739
ast
736740
---
@@ -1741,6 +1745,12 @@ ctypes
17411745
which has been deprecated since Python 3.13.
17421746
(Contributed by Bénédikt Tran in :gh:`133866`.)
17431747

1748+
* Change the :py:attr:`~ctypes._SimpleCData._type_` of
1749+
:class:`~ctypes.c_float_complex`, :class:`~ctypes.c_double_complex` and
1750+
:class:`~ctypes.c_longdouble_complex` from ``F``, ``D`` and ``G`` to ``Zf``,
1751+
``Zd`` and ``Zg`` for compatibility with numpy.
1752+
(Contributed by Victor Stinner in :gh:`148675`.)
1753+
17441754

17451755
datetime
17461756
--------

Lib/ctypes/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,13 @@ class c_longdouble(_SimpleCData):
206206

207207
try:
208208
class c_double_complex(_SimpleCData):
209-
_type_ = "D"
209+
_type_ = "Zd"
210210
_check_size(c_double_complex)
211211
class c_float_complex(_SimpleCData):
212-
_type_ = "F"
212+
_type_ = "Zf"
213213
_check_size(c_float_complex)
214214
class c_longdouble_complex(_SimpleCData):
215-
_type_ = "G"
215+
_type_ = "Zg"
216216
except AttributeError:
217217
pass
218218

Lib/test/test_array.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ class ArraySubclassWithKwargs(array.array):
3131
def __init__(self, typecode, newarg=None):
3232
array.array.__init__(self)
3333

34-
typecodes = 'uwbBhHiIlLfdqQFDe'
34+
typecodes = (
35+
'u', 'w', 'b', 'B', 'h', 'H', 'i', 'I', 'l', 'L',
36+
'f', 'd', 'q', 'Q', 'F', 'D', 'e', 'Zf', 'Zd')
37+
3538

3639
class MiscTest(unittest.TestCase):
3740

@@ -42,8 +45,9 @@ def test_array_is_sequence(self):
4245
def test_bad_constructor(self):
4346
self.assertRaises(TypeError, array.array)
4447
self.assertRaises(TypeError, array.array, spam=42)
45-
self.assertRaises(TypeError, array.array, 'xx')
48+
self.assertRaises(ValueError, array.array, 'xx')
4649
self.assertRaises(ValueError, array.array, 'x')
50+
self.assertRaises(ValueError, array.array, 'Z')
4751

4852
@support.cpython_only
4953
def test_disallow_instantiation(self):
@@ -85,6 +89,12 @@ def __index__(self):
8589
with self.assertRaises(TypeError):
8690
a.fromlist(lst)
8791

92+
def test_typecodes(self):
93+
self.assertIsInstance(array.typecodes, tuple)
94+
for typecode in array.typecodes:
95+
self.assertIsInstance(typecode, str)
96+
self.assertGreaterEqual(len(typecode), 1)
97+
8898

8999
# Machine format codes.
90100
#
@@ -208,6 +218,14 @@ def test_numbers(self):
208218
[9006104071832581.0j, float('inf'), complex('1-infj'), -0.0]),
209219
(['D'], IEEE_754_DOUBLE_COMPLEX_BE, '>DDDD',
210220
[9006104071832581.0j, float('inf'), complex('1-infj'), -0.0]),
221+
(['Zf'], IEEE_754_FLOAT_COMPLEX_LE, '<ZfZfZfZf',
222+
[16711938.0j, float('inf'), complex('1-infj'), -0.0]),
223+
(['Zf'], IEEE_754_FLOAT_COMPLEX_BE, '>ZfZfZfZf',
224+
[16711938.0j, float('inf'), complex('1-infj'), -0.0]),
225+
(['Zd'], IEEE_754_DOUBLE_COMPLEX_LE, '<ZdZdZdZd',
226+
[9006104071832581.0j, float('inf'), complex('1-infj'), -0.0]),
227+
(['Zd'], IEEE_754_DOUBLE_COMPLEX_BE, '>ZdZdZdZd',
228+
[9006104071832581.0j, float('inf'), complex('1-infj'), -0.0]),
211229
)
212230
for testcase in testcases:
213231
valid_typecodes, mformat_code, struct_fmt, values = testcase
@@ -1237,6 +1255,9 @@ def test_free_after_iterating(self):
12371255
support.check_free_after_iterating(self, reversed, array.array,
12381256
(self.typecode,))
12391257

1258+
def test_known_typecode(self):
1259+
self.assertIn(self.typecode, array.typecodes)
1260+
12401261
class StringTest(BaseTest):
12411262

12421263
def test_setitem(self):
@@ -1576,7 +1597,7 @@ def test_nan(self):
15761597
def test_byteswap(self):
15771598
a = array.array(self.typecode, self.example)
15781599
self.assertRaises(TypeError, a.byteswap, 42)
1579-
if a.itemsize in (1, 2, 4, 8):
1600+
if a.itemsize in (1, 2, 4, 8, 16):
15801601
b = array.array(self.typecode, self.example)
15811602
b.byteswap()
15821603
if a.itemsize == 1:
@@ -1631,6 +1652,14 @@ class ComplexDoubleTest(CFPTest, unittest.TestCase):
16311652
typecode = 'D'
16321653
minitemsize = 16
16331654

1655+
class ComplexZfFloatTest(CFPTest, unittest.TestCase):
1656+
typecode = 'Zf'
1657+
minitemsize = 8
1658+
1659+
class ComplexZdDoubleTest(CFPTest, unittest.TestCase):
1660+
typecode = 'Zd'
1661+
minitemsize = 16
1662+
16341663

16351664
class LargeArrayTest(unittest.TestCase):
16361665
typecode = 'b'

Lib/test/test_buffer.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
'h':0, 'H':0, 'i':0, 'I':0,
6868
'l':0, 'L':0, 'n':0, 'N':0,
6969
'e':0, 'f':0, 'd':0, 'P':0,
70-
'F':0, 'D':0
70+
'F':0, 'D':0, 'Zf':0, 'Zd':0,
7171
}
7272

7373
# NumPy does not have 'n' or 'N':
@@ -95,7 +95,9 @@
9595
'e':(-65519, 65520), 'f':(-(1<<63), 1<<63),
9696
'd':(-(1<<1023), 1<<1023),
9797
'F':(-(1<<63), 1<<63),
98-
'D':(-(1<<1023), 1<<1023)
98+
'D':(-(1<<1023), 1<<1023),
99+
'Zf':(-(1<<63), 1<<63),
100+
'Zd':(-(1<<1023), 1<<1023),
99101
}
100102

101103
def native_type_range(fmt):
@@ -110,9 +112,9 @@ def native_type_range(fmt):
110112
lh = (-(1<<63), 1<<63)
111113
elif fmt == 'd':
112114
lh = (-(1<<1023), 1<<1023)
113-
elif fmt == 'F':
115+
elif fmt in ('F', 'Zf'):
114116
lh = (-(1<<63), 1<<63)
115-
elif fmt == 'D':
117+
elif fmt in ('D', 'Zd'):
116118
lh = (-(1<<1023), 1<<1023)
117119
else:
118120
for exp in (128, 127, 64, 63, 32, 31, 16, 15, 8, 7):
@@ -182,18 +184,28 @@ def randrange_fmt(mode, char, obj):
182184
if char in 'efd':
183185
x = struct.pack(char, x)
184186
x = struct.unpack(char, x)[0]
185-
if char in 'FD':
187+
if char in ('F', 'D', 'Zf', 'Zd'):
186188
y = randrange(*fmtdict[mode][char])
187189
x = complex(x, y)
188190
x = struct.pack(char, x)
189191
x = struct.unpack(char, x)[0]
190192
return x
191193

194+
def split_format(fmt):
195+
i = 0
196+
while i < len(fmt):
197+
if fmt[i] == 'Z':
198+
n = 2
199+
else:
200+
n = 1
201+
yield fmt[i:i + n]
202+
i += n
203+
192204
def gen_item(fmt, obj):
193205
"""Return single random item."""
194206
mode, chars = fmt.split('#')
195207
x = []
196-
for c in chars:
208+
for c in split_format(chars):
197209
x.append(randrange_fmt(mode, c, obj))
198210
return x[0] if len(x) == 1 else tuple(x)
199211

@@ -254,9 +266,7 @@ def is_byte_format(fmt):
254266

255267
def is_memoryview_format(fmt):
256268
"""format suitable for memoryview"""
257-
x = len(fmt)
258-
return ((x == 1 or (x == 2 and fmt[0] == '@')) and
259-
fmt[x-1] in MEMORYVIEW)
269+
return fmt.removeprefix('@') in MEMORYVIEW
260270

261271
NON_BYTE_FORMAT = [c for c in fmtdict['@'] if not is_byte_format(c)]
262272

@@ -648,14 +658,22 @@ def ndarray_from_structure(items, fmt, t, flags=0):
648658
return ndarray(items, shape=shape, strides=strides, format=fmt,
649659
offset=offset, flags=ND_WRITABLE|flags)
650660

661+
# Convert PEP 3118 formats to numpy dtypes
662+
FORMAT_TO_DTYPE = {
663+
'Zf': 'F',
664+
'Zd': 'D',
665+
}
666+
651667
def numpy_array_from_structure(items, fmt, t):
652668
"""Return numpy_array from the tuple returned by rand_structure()"""
653669
memlen, itemsize, ndim, shape, strides, offset = t
654670
buf = bytearray(memlen)
655671
for j, v in enumerate(items):
656672
struct.pack_into(fmt, buf, j*itemsize, v)
673+
# Replace Zd/Zf formats with D/F dtypes
674+
dtype = FORMAT_TO_DTYPE.get(fmt, fmt)
657675
return numpy_array(buffer=buf, shape=shape, strides=strides,
658-
dtype=fmt, offset=offset)
676+
dtype=dtype, offset=offset)
659677

660678

661679
# ======================================================================
@@ -3037,7 +3055,7 @@ def test_memoryview_assign(self):
30373055
continue
30383056
m2 = m1.cast(fmt)
30393057
lo, hi = _range
3040-
if fmt in "dfDF":
3058+
if fmt in ("d", "f", "D", "F", "Zd", "Zf"):
30413059
lo, hi = -2**1024, 2**1024
30423060
if fmt != 'P': # PyLong_AsVoidPtr() accepts negative numbers
30433061
self.assertRaises(ValueError, m2.__setitem__, 0, lo-1)

Lib/test/test_ctypes/test_c_simple_type_meta.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,12 @@ def test_bad_type_message(self):
213213
class F(metaclass=PyCSimpleType):
214214
_type_ = "\0"
215215
message = str(cm.exception)
216-
expected_type_chars = list('cbBhHiIlLdDFGfuzZqQPXOv?g')
217-
if not hasattr(ctypes, 'c_float_complex'):
218-
expected_type_chars.remove('F')
219-
expected_type_chars.remove('D')
220-
expected_type_chars.remove('G')
216+
expected_type_chars = list('cbBhHiIlLdfuzZqQPXOv?g')
221217
if not MS_WINDOWS:
222218
expected_type_chars.remove('X')
223219
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
220+
if hasattr(ctypes, 'c_float_complex'):
221+
self.assertIn("'Zf', 'Zd', 'Zg'", message)
224222

225223
def test_creating_pointer_in_dunder_init_3(self):
226224
"""Check if interfcase subclasses properly creates according internal

Lib/test/test_ctypes/test_numbers.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,24 @@ def test_floats(self):
119119
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
120120
"requires C11 complex type")
121121
def test_complex(self):
122-
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
123-
ctypes.c_longdouble_complex]:
122+
for format, t in [
123+
('Zd', ctypes.c_double_complex),
124+
('Zf', ctypes.c_float_complex),
125+
('Zg', ctypes.c_longdouble_complex),
126+
]:
124127
self.assertEqual(t(1).value, 1+0j)
125128
self.assertEqual(t(1.0).value, 1+0j)
126129
self.assertEqual(t(1+0.125j).value, 1+0.125j)
127130
self.assertEqual(t(IndexLike()).value, 2+0j)
128131
self.assertEqual(t(FloatLike()).value, 2+0j)
129132
self.assertEqual(t(ComplexLike()).value, 1+1j)
130133

134+
prefix = '>' if sys.byteorder == 'big' else '<'
135+
num = t(1.0)
136+
self.assertEqual(memoryview(num).format, prefix + format)
137+
array = (t * 3)()
138+
self.assertEqual(memoryview(array).format, prefix + format)
139+
131140
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
132141
"requires C11 complex type")
133142
def test_complex_round_trip(self):

0 commit comments

Comments
 (0)