6666 '?' :0 , 'c' :0 , 'b' :0 , 'B' :0 ,
6767 'h' :0 , 'H' :0 , 'i' :0 , 'I' :0 ,
6868 'l' :0 , 'L' :0 , 'n' :0 , 'N' :0 ,
69- 'e' :0 , 'f' :0 , 'd' :0 , 'P' :0
69+ 'e' :0 , 'f' :0 , 'd' :0 , 'P' :0 ,
70+ 'Zf' :0 , 'Zd' :0 ,
7071}
7172
7273# NumPy does not have 'n' or 'N':
9293 'l' :(- (1 << 31 ), 1 << 31 ), 'L' :(0 , 1 << 32 ),
9394 'q' :(- (1 << 63 ), 1 << 63 ), 'Q' :(0 , 1 << 64 ),
9495 'e' :(- 65519 , 65520 ), 'f' :(- (1 << 63 ), 1 << 63 ),
95- 'd' :(- (1 << 1023 ), 1 << 1023 )
96+ 'd' :(- (1 << 1023 ), 1 << 1023 ),
97+ 'Zf' :(- (1 << 63 ), 1 << 63 ),
98+ 'Zd' :(- (1 << 1023 ), 1 << 1023 ),
9699}
97100
98101def native_type_range (fmt ):
@@ -107,6 +110,10 @@ def native_type_range(fmt):
107110 lh = (- (1 << 63 ), 1 << 63 )
108111 elif fmt == 'd' :
109112 lh = (- (1 << 1023 ), 1 << 1023 )
113+ elif fmt == 'Zf' :
114+ lh = (- (1 << 63 ), 1 << 63 )
115+ elif fmt == 'Zd' :
116+ lh = (- (1 << 1023 ), 1 << 1023 )
110117 else :
111118 for exp in (128 , 127 , 64 , 63 , 32 , 31 , 16 , 15 , 8 , 7 ):
112119 try :
@@ -136,7 +143,7 @@ def native_type_range(fmt):
136143# Format codes supported by array.array
137144ARRAY = NATIVE .copy ()
138145for k in NATIVE :
139- if not k in "bBhHiIlLfd" :
146+ if k not in list ( "bBhHiIlLefd" ) + [ 'Zf' , 'Zd' ] :
140147 del ARRAY [k ]
141148
142149BYTEFMT = NATIVE .copy ()
@@ -175,13 +182,28 @@ def randrange_fmt(mode, char, obj):
175182 if char in 'efd' :
176183 x = struct .pack (char , x )
177184 x = struct .unpack (char , x )[0 ]
185+ if char in ('Zf' , 'Zd' ):
186+ y = randrange (* fmtdict [mode ][char ])
187+ x = complex (x , y )
188+ x = struct .pack (char , x )
189+ x = struct .unpack (char , x )[0 ]
178190 return x
179191
192+ def split_format (fmt ):
193+ i = 0
194+ while i < len (fmt ):
195+ if fmt [i ] == 'Z' :
196+ n = 2
197+ else :
198+ n = 1
199+ yield fmt [i :i + n ]
200+ i += n
201+
180202def gen_item (fmt , obj ):
181203 """Return single random item."""
182204 mode , chars = fmt .split ('#' )
183205 x = []
184- for c in chars :
206+ for c in split_format ( chars ) :
185207 x .append (randrange_fmt (mode , c , obj ))
186208 return x [0 ] if len (x ) == 1 else tuple (x )
187209
@@ -242,9 +264,7 @@ def is_byte_format(fmt):
242264
243265def is_memoryview_format (fmt ):
244266 """format suitable for memoryview"""
245- x = len (fmt )
246- return ((x == 1 or (x == 2 and fmt [0 ] == '@' )) and
247- fmt [x - 1 ] in MEMORYVIEW )
267+ return fmt .removeprefix ('@' ) in MEMORYVIEW
248268
249269NON_BYTE_FORMAT = [c for c in fmtdict ['@' ] if not is_byte_format (c )]
250270
@@ -636,14 +656,22 @@ def ndarray_from_structure(items, fmt, t, flags=0):
636656 return ndarray (items , shape = shape , strides = strides , format = fmt ,
637657 offset = offset , flags = ND_WRITABLE | flags )
638658
659+ # Convert PEP 3118 formats to numpy dtypes
660+ FORMAT_TO_DTYPE = {
661+ 'Zf' : 'F' ,
662+ 'Zd' : 'D' ,
663+ }
664+
639665def numpy_array_from_structure (items , fmt , t ):
640666 """Return numpy_array from the tuple returned by rand_structure()"""
641667 memlen , itemsize , ndim , shape , strides , offset = t
642668 buf = bytearray (memlen )
643669 for j , v in enumerate (items ):
644670 struct .pack_into (fmt , buf , j * itemsize , v )
671+ # Replace Zd/Zf formats with D/F dtypes
672+ dtype = FORMAT_TO_DTYPE .get (fmt , fmt )
645673 return numpy_array (buffer = buf , shape = shape , strides = strides ,
646- dtype = fmt , offset = offset )
674+ dtype = dtype , offset = offset )
647675
648676
649677# ======================================================================
@@ -3015,7 +3043,7 @@ def test_memoryview_assign(self):
30153043 m = memoryview (nd )
30163044 self .assertRaises (TypeError , m .__setitem__ , 0 , 100 )
30173045
3018- ex = ndarray (list (range (120 )), shape = [1 ,2 ,3 ,4 ,5 ], flags = ND_WRITABLE )
3046+ ex = ndarray (list (range (144 )), shape = [1 ,2 ,3 ,4 ,6 ], flags = ND_WRITABLE )
30193047 m1 = memoryview (ex )
30203048
30213049 for fmt , _range in fmtdict ['@' ].items ():
@@ -3025,7 +3053,7 @@ def test_memoryview_assign(self):
30253053 continue
30263054 m2 = m1 .cast (fmt )
30273055 lo , hi = _range
3028- if fmt == 'd' or fmt == 'f' :
3056+ if fmt in ( "d" , "f" , "Zd" , "Zf" ) :
30293057 lo , hi = - 2 ** 1024 , 2 ** 1024
30303058 if fmt != 'P' : # PyLong_AsVoidPtr() accepts negative numbers
30313059 self .assertRaises (ValueError , m2 .__setitem__ , 0 , lo - 1 )
@@ -4447,6 +4475,43 @@ def test_bytearray_release_buffer_read_flag(self):
44474475 with self .assertRaises (SystemError ):
44484476 obj .__buffer__ (inspect .BufferFlags .WRITE )
44494477
4478+ @support .cpython_only
4479+ @unittest .skipIf (_testcapi is None , "requires _testcapi" )
4480+ def test_bytearray_alignment (self ):
4481+ # gh-140557: pointer alignment of buffers including empty allocation
4482+ # should be at least to `size_t`.
4483+ align = struct .calcsize ("N" )
4484+ cases = [
4485+ bytearray (),
4486+ bytearray (1 ),
4487+ bytearray (b"0123456789abcdef" ),
4488+ bytearray (16 ),
4489+ ]
4490+ ptrs = [_testcapi .buffer_pointer_as_int (array ) for array in cases ]
4491+ self .assertEqual ([ptr % align for ptr in ptrs ], [0 ]* len (ptrs ))
4492+
4493+ @support .cpython_only
4494+ @unittest .skipIf (_testcapi is None , "requires _testcapi" )
4495+ def test_array_alignment (self ):
4496+ # gh-140557: pointer alignment of buffers including empty allocation
4497+ # should match the maximum array alignment.
4498+ formats = [fmt for fmt in ARRAY
4499+ if struct .calcsize (fmt ) <= struct .calcsize ('P' )]
4500+ align = max (struct .calcsize (fmt ) for fmt in formats )
4501+ cases = [array .array (fmt ) for fmt in formats ]
4502+ # Empty arrays
4503+ self .assertEqual (
4504+ [_testcapi .buffer_pointer_as_int (case ) % align for case in cases ],
4505+ [0 ] * len (cases ),
4506+ )
4507+ for case in cases :
4508+ case .append (0 )
4509+ # Allocated arrays
4510+ self .assertEqual (
4511+ [_testcapi .buffer_pointer_as_int (case ) % align for case in cases ],
4512+ [0 ] * len (cases ),
4513+ )
4514+
44504515 @support .cpython_only
44514516 def test_pybuffer_size_from_format (self ):
44524517 # basic tests
0 commit comments