Skip to content

Commit feb9f5a

Browse files
committed
gh-112014: correct buffer protocol support in ctypes (use native formats)
This allows better interoperation with the memoryview, e.g. support for ctypes arrays. Just like NumPy arrays, they also don't specify endianness: ```pycon >>> import numpy as np >>> memoryview(np.ndarray(3, dtype=np.float16)).format 'e' ``` But keep specified byteorder for byte-swapped types.
1 parent 87b120f commit feb9f5a

4 files changed

Lines changed: 74 additions & 78 deletions

File tree

Lib/test/test_ctypes/test_numbers.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,10 @@ def test_complex(self):
131131
self.assertEqual(t(FloatLike()).value, 2+0j)
132132
self.assertEqual(t(ComplexLike()).value, 1+1j)
133133

134-
prefix = '>' if sys.byteorder == 'big' else '<'
135134
num = t(1.0)
136-
self.assertEqual(memoryview(num).format, prefix + format)
135+
self.assertEqual(memoryview(num).format, format)
137136
array = (t * 3)()
138-
self.assertEqual(memoryview(array).format, prefix + format)
137+
self.assertEqual(memoryview(array).format, format)
139138

140139
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
141140
"requires C11 complex type")

Lib/test/test_ctypes/test_pep3118.py

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import re
21
import sys
32
import unittest
43
from ctypes import (CFUNCTYPE, POINTER, sizeof, Union,
@@ -9,29 +8,12 @@
98
c_bool, c_float, c_double, c_longdouble, py_object)
109

1110

12-
if sys.byteorder == "little":
13-
THIS_ENDIAN = "<"
14-
OTHER_ENDIAN = ">"
15-
else:
16-
THIS_ENDIAN = ">"
17-
OTHER_ENDIAN = "<"
18-
19-
20-
def normalize(format):
21-
# Remove current endian specifier and white space from a format
22-
# string
23-
if format is None:
24-
return ""
25-
format = format.replace(OTHER_ENDIAN, THIS_ENDIAN)
26-
return re.sub(r"\s", "", format)
27-
28-
2911
class Test(unittest.TestCase):
3012
def test_native_types(self):
3113
for tp, fmt, shape, itemtp in native_types:
3214
ob = tp()
3315
v = memoryview(ob)
34-
self.assertEqual(normalize(v.format), normalize(fmt))
16+
self.assertEqual(v.format, fmt)
3517
if shape:
3618
self.assertEqual(len(v), shape[0])
3719
else:
@@ -73,6 +55,15 @@ def test_endian_types(self):
7355
n = n * dim
7456
self.assertEqual(n * v.itemsize, len(v.tobytes()))
7557

58+
def test_memoryview_supports_ctypes_arrays(self):
59+
ArrayType = c_int * 5
60+
a = ArrayType(123, 42, 1, 2, 3)
61+
m = memoryview(a)
62+
self.assertEqual(m.shape, (5,))
63+
self.assertEqual(m.format, c_int._type_)
64+
self.assertEqual(list(m), [123, 42, 1, 2, 3])
65+
self.assertEqual(m[1], 42)
66+
7667

7768
# define some structure classes
7869

@@ -124,8 +115,7 @@ class Complete(Structure):
124115

125116
################################################################
126117
#
127-
# This table contains format strings as they look on little endian
128-
# machines. The test replaces '<' with '>' on big endian machines.
118+
# This table contains format strings with native endianness.
129119
#
130120

131121
# Platform-specific type codes
@@ -160,67 +150,67 @@ class Complete(Structure):
160150

161151
## simple types
162152

163-
(c_char, "<c", (), c_char),
164-
(c_byte, "<b", (), c_byte),
165-
(c_ubyte, "<B", (), c_ubyte),
166-
(c_short, "<" + s_short, (), c_short),
167-
(c_ushort, "<" + s_ushort, (), c_ushort),
153+
(c_char, "c", (), c_char),
154+
(c_byte, "b", (), c_byte),
155+
(c_ubyte, "B", (), c_ubyte),
156+
(c_short, s_short, (), c_short),
157+
(c_ushort, s_ushort, (), c_ushort),
168158

169-
(c_int, "<" + s_int, (), c_int),
170-
(c_uint, "<" + s_uint, (), c_uint),
159+
(c_int, s_int, (), c_int),
160+
(c_uint, s_uint, (), c_uint),
171161

172-
(c_long, "<" + s_long, (), c_long),
173-
(c_ulong, "<" + s_ulong, (), c_ulong),
162+
(c_long, s_long, (), c_long),
163+
(c_ulong, s_ulong, (), c_ulong),
174164

175-
(c_longlong, "<" + s_longlong, (), c_longlong),
176-
(c_ulonglong, "<" + s_ulonglong, (), c_ulonglong),
165+
(c_longlong, s_longlong, (), c_longlong),
166+
(c_ulonglong, s_ulonglong, (), c_ulonglong),
177167

178-
(c_float, "<f", (), c_float),
179-
(c_double, "<d", (), c_double),
168+
(c_float, "f", (), c_float),
169+
(c_double, "d", (), c_double),
180170

181-
(c_longdouble, "<" + s_longdouble, (), c_longdouble),
171+
(c_longdouble, s_longdouble, (), c_longdouble),
182172

183-
(c_bool, "<" + s_bool, (), c_bool),
184-
(py_object, "<O", (), py_object),
173+
(c_bool, s_bool, (), c_bool),
174+
(py_object, "O", (), py_object),
185175

186176
## pointers
187177

188-
(POINTER(c_byte), "&<b", (), POINTER(c_byte)),
189-
(POINTER(POINTER(c_long)), "&&<" + s_long, (), POINTER(POINTER(c_long))),
178+
(POINTER(c_byte), "&b", (), POINTER(c_byte)),
179+
(POINTER(POINTER(c_long)), "&&" + s_long, (), POINTER(POINTER(c_long))),
190180

191181
## arrays and pointers
192182

193-
(c_double * 4, "<d", (4,), c_double),
194-
(c_double * 0, "<d", (0,), c_double),
195-
(c_float * 4 * 3 * 2, "<f", (2,3,4), c_float),
196-
(c_float * 4 * 0 * 2, "<f", (2,0,4), c_float),
197-
(POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)),
198-
(POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)),
199-
(POINTER(c_short * 2), "&(2)<" + s_short, (), POINTER(c_short)),
183+
(c_double * 4, "d", (4,), c_double),
184+
(c_double * 0, "d", (0,), c_double),
185+
(c_float * 4 * 3 * 2, "f", (2,3,4), c_float),
186+
(c_float * 4 * 0 * 2, "f", (2,0,4), c_float),
187+
(POINTER(c_short) * 2, "&" + s_short, (2,), POINTER(c_short)),
188+
(POINTER(c_short) * 2 * 3, "&" + s_short, (3,2,), POINTER(c_short)),
189+
(POINTER(c_short * 2), "&(2)" + s_short, (), POINTER(c_short)),
200190

201191
## structures and unions
202192

203-
(Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), Point2),
204-
(Point, "T{<l:x:<l:y:}".replace('l', s_long), (), Point),
205-
(PackedPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), PackedPoint),
206-
(PointMidPad, "T{<b:x:3x<I:y:}".replace('I', s_uint), (), PointMidPad),
207-
(PackedPointMidPad, "T{<b:x:x<Q:y:}", (), PackedPointMidPad),
208-
(PointEndPad, "T{<I:x:<b:y:3x}".replace('I', s_uint), (), PointEndPad),
209-
(PackedPointEndPad, "T{<Q:x:<b:y:x}", (), PackedPointEndPad),
210-
(EmptyStruct, "T{}", (), EmptyStruct),
193+
(Point2, "T{l:x:l:y:}".replace('l', s_long), (), Point2),
194+
(Point, "T{l:x:l:y:}".replace('l', s_long), (), Point),
195+
(PackedPoint, "T{l:x:l:y:}".replace('l', s_long), (), PackedPoint),
196+
(PointMidPad, "T{b:x:3xI:y:}".replace('I', s_uint), (), PointMidPad),
197+
(PackedPointMidPad, "T{b:x:xQ:y:}", (), PackedPointMidPad),
198+
(PointEndPad, "T{I:x:b:y:3x}".replace('I', s_uint), (), PointEndPad),
199+
(PackedPointEndPad, "T{Q:x:b:y:x}", (), PackedPointEndPad),
200+
(EmptyStruct, "T{}", (), EmptyStruct),
211201
# the pep doesn't support unions
212-
(aUnion, "B", (), aUnion),
202+
(aUnion, "B", (), aUnion),
213203
# structure with sub-arrays
214-
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), StructWithArrays),
215-
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays),
204+
(StructWithArrays, "T{(2,3)l:x:(4)T{l:x:l:y:}:y:}".replace('l', s_long), (), StructWithArrays),
205+
(StructWithArrays * 3, "T{(2,3)l:x:(4)T{l:x:l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays),
216206

217207
## pointer to incomplete structure
218208
(Incomplete, "B", (), Incomplete),
219209
(POINTER(Incomplete), "&B", (), POINTER(Incomplete)),
220210

221211
# 'Complete' is a structure that starts incomplete, but is completed after the
222212
# pointer type to it has been created.
223-
(Complete, "T{<l:a:}".replace('l', s_long), (), Complete),
213+
(Complete, "T{l:a:}".replace('l', s_long), (), Complete),
224214
# Unfortunately the pointer format string is not fixed...
225215
(POINTER(Complete), "&B", (), POINTER(Complete)),
226216

@@ -241,12 +231,20 @@ class LEPoint(LittleEndianStructure):
241231

242232
# This table contains format strings as they really look, on both big
243233
# and little endian machines.
244-
endian_types = [
245-
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint),
246-
(LEPoint * 1, "T{<l:x:<l:y:}".replace('l', s_long), (1,), LEPoint),
247-
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
248-
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
249-
]
234+
if sys.byteorder == "little":
235+
endian_types = [
236+
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint),
237+
(LEPoint * 1, "T{l:x:l:y:}".replace('l', s_long), (1,), LEPoint),
238+
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
239+
(POINTER(LEPoint), "&T{l:x:l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
240+
]
241+
else:
242+
endian_types = [
243+
(BEPoint * 1, "T{l:x:l:y:}".replace('l', s_long), (1,), BEPoint),
244+
(LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), LEPoint),
245+
(POINTER(BEPoint), "&T{l:x:l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
246+
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
247+
]
250248

251249

252250
if __name__ == "__main__":
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Correct the :ref:`buffer protocol <bufferobjects>` support in the
2+
:mod:`ctypes` module to use the machine’s native format and byte order,
3+
rather than explicitly specify endianness (by ``'<'`` or ``'>'``). The
4+
later kept for byte-swapped types. Patch by Sergey B Kirpichev.

Modules/_ctypes/_ctypes.c

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ _PyDict_GetItemProxy(PyObject *dict, PyObject *key, PyObject **presult)
267267
later on.
268268
*/
269269
static char *
270-
_ctypes_alloc_format_string_for_type(const char *code, int big_endian)
270+
_ctypes_alloc_format_string_for_type(const char *code)
271271
{
272272
const char *pep_code = NULL;
273273

@@ -310,14 +310,13 @@ _ctypes_alloc_format_string_for_type(const char *code, int big_endian)
310310
break;
311311
}
312312

313-
char *result = PyMem_Malloc(1 + strlen(pep_code) + 1);
313+
char *result = PyMem_Malloc(1 + strlen(pep_code));
314314
if (result == NULL) {
315315
PyErr_NoMemory();
316316
return NULL;
317317
}
318318

319-
result[0] = big_endian ? '>' : '<';
320-
strcpy(result + 1, pep_code);
319+
strcpy(result, pep_code);
321320
return result;
322321
}
323322

@@ -2405,11 +2404,7 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
24052404
stginfo->size = fmt->pffi_type->size;
24062405
stginfo->setfunc = fmt->setfunc;
24072406
stginfo->getfunc = fmt->getfunc;
2408-
#ifdef WORDS_BIGENDIAN
2409-
stginfo->format = _ctypes_alloc_format_string_for_type(proto_str, 1);
2410-
#else
2411-
stginfo->format = _ctypes_alloc_format_string_for_type(proto_str, 0);
2412-
#endif
2407+
stginfo->format = _ctypes_alloc_format_string_for_type(proto_str);
24132408
if (stginfo->format == NULL) {
24142409
Py_DECREF(proto);
24152410
return -1;
@@ -2504,14 +2499,14 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds)
25042499
PyObject_SetAttrString(swapped, "__ctype_be__", self);
25052500
PyObject_SetAttrString(swapped, "__ctype_le__", swapped);
25062501
/* We are creating the type for the OTHER endian */
2507-
sw_info->format = _ctypes_alloc_format_string("<", stginfo->format+1);
2502+
sw_info->format = _ctypes_alloc_format_string("<", stginfo->format);
25082503
#else
25092504
PyObject_SetAttrString(self, "__ctype_be__", swapped);
25102505
PyObject_SetAttrString(self, "__ctype_le__", self);
25112506
PyObject_SetAttrString(swapped, "__ctype_le__", self);
25122507
PyObject_SetAttrString(swapped, "__ctype_be__", swapped);
25132508
/* We are creating the type for the OTHER endian */
2514-
sw_info->format = _ctypes_alloc_format_string(">", stginfo->format+1);
2509+
sw_info->format = _ctypes_alloc_format_string(">", stginfo->format);
25152510
#endif
25162511
Py_DECREF(swapped);
25172512
if (PyErr_Occurred()) {

0 commit comments

Comments
 (0)