Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/numpy/ctypeslib.py : 17%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2============================
3``ctypes`` Utility Functions
4============================
6See Also
7---------
8load_library : Load a C library.
9ndpointer : Array restype/argtype with verification.
10as_ctypes : Create a ctypes array from an ndarray.
11as_array : Create an ndarray from a ctypes array.
13References
14----------
15.. [1] "SciPy Cookbook: ctypes", https://scipy-cookbook.readthedocs.io/items/Ctypes.html
17Examples
18--------
19Load the C library:
21>>> _lib = np.ctypeslib.load_library('libmystuff', '.') #doctest: +SKIP
23Our result type, an ndarray that must be of type double, be 1-dimensional
24and is C-contiguous in memory:
26>>> array_1d_double = np.ctypeslib.ndpointer(
27... dtype=np.double,
28... ndim=1, flags='CONTIGUOUS') #doctest: +SKIP
30Our C-function typically takes an array and updates its values
31in-place. For example::
33 void foo_func(double* x, int length)
34 {
35 int i;
36 for (i = 0; i < length; i++) {
37 x[i] = i*i;
38 }
39 }
41We wrap it using:
43>>> _lib.foo_func.restype = None #doctest: +SKIP
44>>> _lib.foo_func.argtypes = [array_1d_double, c_int] #doctest: +SKIP
46Then, we're ready to call ``foo_func``:
48>>> out = np.empty(15, dtype=np.double)
49>>> _lib.foo_func(out, len(out)) #doctest: +SKIP
51"""
52__all__ = ['load_library', 'ndpointer', 'ctypes_load_library',
53 'c_intp', 'as_ctypes', 'as_array']
55import os
56from numpy import (
57 integer, ndarray, dtype as _dtype, deprecate, array, frombuffer
58)
59from numpy.core.multiarray import _flagdict, flagsobj
61try:
62 import ctypes
63except ImportError:
64 ctypes = None
66if ctypes is None:
67 def _dummy(*args, **kwds):
68 """
69 Dummy object that raises an ImportError if ctypes is not available.
71 Raises
72 ------
73 ImportError
74 If ctypes is not available.
76 """
77 raise ImportError("ctypes is not available.")
78 ctypes_load_library = _dummy
79 load_library = _dummy
80 as_ctypes = _dummy
81 as_array = _dummy
82 from numpy import intp as c_intp
83 _ndptr_base = object
84else:
85 import numpy.core._internal as nic
86 c_intp = nic._getintp_ctype()
87 del nic
88 _ndptr_base = ctypes.c_void_p
90 # Adapted from Albert Strasheim
91 def load_library(libname, loader_path):
92 """
93 It is possible to load a library using
94 >>> lib = ctypes.cdll[<full_path_name>] # doctest: +SKIP
96 But there are cross-platform considerations, such as library file extensions,
97 plus the fact Windows will just load the first library it finds with that name.
98 NumPy supplies the load_library function as a convenience.
100 Parameters
101 ----------
102 libname : str
103 Name of the library, which can have 'lib' as a prefix,
104 but without an extension.
105 loader_path : str
106 Where the library can be found.
108 Returns
109 -------
110 ctypes.cdll[libpath] : library object
111 A ctypes library object
113 Raises
114 ------
115 OSError
116 If there is no library with the expected extension, or the
117 library is defective and cannot be loaded.
118 """
119 if ctypes.__version__ < '1.0.1':
120 import warnings
121 warnings.warn("All features of ctypes interface may not work "
122 "with ctypes < 1.0.1", stacklevel=2)
124 ext = os.path.splitext(libname)[1]
125 if not ext:
126 # Try to load library with platform-specific name, otherwise
127 # default to libname.[so|pyd]. Sometimes, these files are built
128 # erroneously on non-linux platforms.
129 from numpy.distutils.misc_util import get_shared_lib_extension
130 so_ext = get_shared_lib_extension()
131 libname_ext = [libname + so_ext]
132 # mac, windows and linux >= py3.2 shared library and loadable
133 # module have different extensions so try both
134 so_ext2 = get_shared_lib_extension(is_python_ext=True)
135 if not so_ext2 == so_ext:
136 libname_ext.insert(0, libname + so_ext2)
137 else:
138 libname_ext = [libname]
140 loader_path = os.path.abspath(loader_path)
141 if not os.path.isdir(loader_path):
142 libdir = os.path.dirname(loader_path)
143 else:
144 libdir = loader_path
146 for ln in libname_ext:
147 libpath = os.path.join(libdir, ln)
148 if os.path.exists(libpath):
149 try:
150 return ctypes.cdll[libpath]
151 except OSError:
152 ## defective lib file
153 raise
154 ## if no successful return in the libname_ext loop:
155 raise OSError("no file with expected extension")
157 ctypes_load_library = deprecate(load_library, 'ctypes_load_library',
158 'load_library')
160def _num_fromflags(flaglist):
161 num = 0
162 for val in flaglist:
163 num += _flagdict[val]
164 return num
166_flagnames = ['C_CONTIGUOUS', 'F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE',
167 'OWNDATA', 'UPDATEIFCOPY', 'WRITEBACKIFCOPY']
168def _flags_fromnum(num):
169 res = []
170 for key in _flagnames:
171 value = _flagdict[key]
172 if (num & value):
173 res.append(key)
174 return res
177class _ndptr(_ndptr_base):
178 @classmethod
179 def from_param(cls, obj):
180 if not isinstance(obj, ndarray):
181 raise TypeError("argument must be an ndarray")
182 if cls._dtype_ is not None \
183 and obj.dtype != cls._dtype_:
184 raise TypeError("array must have data type %s" % cls._dtype_)
185 if cls._ndim_ is not None \
186 and obj.ndim != cls._ndim_:
187 raise TypeError("array must have %d dimension(s)" % cls._ndim_)
188 if cls._shape_ is not None \
189 and obj.shape != cls._shape_:
190 raise TypeError("array must have shape %s" % str(cls._shape_))
191 if cls._flags_ is not None \
192 and ((obj.flags.num & cls._flags_) != cls._flags_):
193 raise TypeError("array must have flags %s" %
194 _flags_fromnum(cls._flags_))
195 return obj.ctypes
198class _concrete_ndptr(_ndptr):
199 """
200 Like _ndptr, but with `_shape_` and `_dtype_` specified.
202 Notably, this means the pointer has enough information to reconstruct
203 the array, which is not generally true.
204 """
205 def _check_retval_(self):
206 """
207 This method is called when this class is used as the .restype
208 attribute for a shared-library function, to automatically wrap the
209 pointer into an array.
210 """
211 return self.contents
213 @property
214 def contents(self):
215 """
216 Get an ndarray viewing the data pointed to by this pointer.
218 This mirrors the `contents` attribute of a normal ctypes pointer
219 """
220 full_dtype = _dtype((self._dtype_, self._shape_))
221 full_ctype = ctypes.c_char * full_dtype.itemsize
222 buffer = ctypes.cast(self, ctypes.POINTER(full_ctype)).contents
223 return frombuffer(buffer, dtype=full_dtype).squeeze(axis=0)
226# Factory for an array-checking class with from_param defined for
227# use with ctypes argtypes mechanism
228_pointer_type_cache = {}
229def ndpointer(dtype=None, ndim=None, shape=None, flags=None):
230 """
231 Array-checking restype/argtypes.
233 An ndpointer instance is used to describe an ndarray in restypes
234 and argtypes specifications. This approach is more flexible than
235 using, for example, ``POINTER(c_double)``, since several restrictions
236 can be specified, which are verified upon calling the ctypes function.
237 These include data type, number of dimensions, shape and flags. If a
238 given array does not satisfy the specified restrictions,
239 a ``TypeError`` is raised.
241 Parameters
242 ----------
243 dtype : data-type, optional
244 Array data-type.
245 ndim : int, optional
246 Number of array dimensions.
247 shape : tuple of ints, optional
248 Array shape.
249 flags : str or tuple of str
250 Array flags; may be one or more of:
252 - C_CONTIGUOUS / C / CONTIGUOUS
253 - F_CONTIGUOUS / F / FORTRAN
254 - OWNDATA / O
255 - WRITEABLE / W
256 - ALIGNED / A
257 - WRITEBACKIFCOPY / X
258 - UPDATEIFCOPY / U
260 Returns
261 -------
262 klass : ndpointer type object
263 A type object, which is an ``_ndtpr`` instance containing
264 dtype, ndim, shape and flags information.
266 Raises
267 ------
268 TypeError
269 If a given array does not satisfy the specified restrictions.
271 Examples
272 --------
273 >>> clib.somefunc.argtypes = [np.ctypeslib.ndpointer(dtype=np.float64,
274 ... ndim=1,
275 ... flags='C_CONTIGUOUS')]
276 ... #doctest: +SKIP
277 >>> clib.somefunc(np.array([1, 2, 3], dtype=np.float64))
278 ... #doctest: +SKIP
280 """
282 # normalize dtype to an Optional[dtype]
283 if dtype is not None:
284 dtype = _dtype(dtype)
286 # normalize flags to an Optional[int]
287 num = None
288 if flags is not None:
289 if isinstance(flags, str):
290 flags = flags.split(',')
291 elif isinstance(flags, (int, integer)):
292 num = flags
293 flags = _flags_fromnum(num)
294 elif isinstance(flags, flagsobj):
295 num = flags.num
296 flags = _flags_fromnum(num)
297 if num is None:
298 try:
299 flags = [x.strip().upper() for x in flags]
300 except Exception:
301 raise TypeError("invalid flags specification")
302 num = _num_fromflags(flags)
304 # normalize shape to an Optional[tuple]
305 if shape is not None:
306 try:
307 shape = tuple(shape)
308 except TypeError:
309 # single integer -> 1-tuple
310 shape = (shape,)
312 cache_key = (dtype, ndim, shape, num)
314 try:
315 return _pointer_type_cache[cache_key]
316 except KeyError:
317 pass
319 # produce a name for the new type
320 if dtype is None:
321 name = 'any'
322 elif dtype.names is not None:
323 name = str(id(dtype))
324 else:
325 name = dtype.str
326 if ndim is not None:
327 name += "_%dd" % ndim
328 if shape is not None:
329 name += "_"+"x".join(str(x) for x in shape)
330 if flags is not None:
331 name += "_"+"_".join(flags)
333 if dtype is not None and shape is not None:
334 base = _concrete_ndptr
335 else:
336 base = _ndptr
338 klass = type("ndpointer_%s"%name, (base,),
339 {"_dtype_": dtype,
340 "_shape_" : shape,
341 "_ndim_" : ndim,
342 "_flags_" : num})
343 _pointer_type_cache[cache_key] = klass
344 return klass
347if ctypes is not None:
348 def _ctype_ndarray(element_type, shape):
349 """ Create an ndarray of the given element type and shape """
350 for dim in shape[::-1]:
351 element_type = dim * element_type
352 # prevent the type name include np.ctypeslib
353 element_type.__module__ = None
354 return element_type
357 def _get_scalar_type_map():
358 """
359 Return a dictionary mapping native endian scalar dtype to ctypes types
360 """
361 ct = ctypes
362 simple_types = [
363 ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong,
364 ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong,
365 ct.c_float, ct.c_double,
366 ct.c_bool,
367 ]
368 return {_dtype(ctype): ctype for ctype in simple_types}
371 _scalar_type_map = _get_scalar_type_map()
374 def _ctype_from_dtype_scalar(dtype):
375 # swapping twice ensure that `=` is promoted to <, >, or |
376 dtype_with_endian = dtype.newbyteorder('S').newbyteorder('S')
377 dtype_native = dtype.newbyteorder('=')
378 try:
379 ctype = _scalar_type_map[dtype_native]
380 except KeyError:
381 raise NotImplementedError(
382 "Converting {!r} to a ctypes type".format(dtype)
383 )
385 if dtype_with_endian.byteorder == '>':
386 ctype = ctype.__ctype_be__
387 elif dtype_with_endian.byteorder == '<':
388 ctype = ctype.__ctype_le__
390 return ctype
393 def _ctype_from_dtype_subarray(dtype):
394 element_dtype, shape = dtype.subdtype
395 ctype = _ctype_from_dtype(element_dtype)
396 return _ctype_ndarray(ctype, shape)
399 def _ctype_from_dtype_structured(dtype):
400 # extract offsets of each field
401 field_data = []
402 for name in dtype.names:
403 field_dtype, offset = dtype.fields[name][:2]
404 field_data.append((offset, name, _ctype_from_dtype(field_dtype)))
406 # ctypes doesn't care about field order
407 field_data = sorted(field_data, key=lambda f: f[0])
409 if len(field_data) > 1 and all(offset == 0 for offset, name, ctype in field_data):
410 # union, if multiple fields all at address 0
411 size = 0
412 _fields_ = []
413 for offset, name, ctype in field_data:
414 _fields_.append((name, ctype))
415 size = max(size, ctypes.sizeof(ctype))
417 # pad to the right size
418 if dtype.itemsize != size:
419 _fields_.append(('', ctypes.c_char * dtype.itemsize))
421 # we inserted manual padding, so always `_pack_`
422 return type('union', (ctypes.Union,), dict(
423 _fields_=_fields_,
424 _pack_=1,
425 __module__=None,
426 ))
427 else:
428 last_offset = 0
429 _fields_ = []
430 for offset, name, ctype in field_data:
431 padding = offset - last_offset
432 if padding < 0:
433 raise NotImplementedError("Overlapping fields")
434 if padding > 0:
435 _fields_.append(('', ctypes.c_char * padding))
437 _fields_.append((name, ctype))
438 last_offset = offset + ctypes.sizeof(ctype)
441 padding = dtype.itemsize - last_offset
442 if padding > 0:
443 _fields_.append(('', ctypes.c_char * padding))
445 # we inserted manual padding, so always `_pack_`
446 return type('struct', (ctypes.Structure,), dict(
447 _fields_=_fields_,
448 _pack_=1,
449 __module__=None,
450 ))
453 def _ctype_from_dtype(dtype):
454 if dtype.fields is not None:
455 return _ctype_from_dtype_structured(dtype)
456 elif dtype.subdtype is not None:
457 return _ctype_from_dtype_subarray(dtype)
458 else:
459 return _ctype_from_dtype_scalar(dtype)
462 def as_ctypes_type(dtype):
463 r"""
464 Convert a dtype into a ctypes type.
466 Parameters
467 ----------
468 dtype : dtype
469 The dtype to convert
471 Returns
472 -------
473 ctype
474 A ctype scalar, union, array, or struct
476 Raises
477 ------
478 NotImplementedError
479 If the conversion is not possible
481 Notes
482 -----
483 This function does not losslessly round-trip in either direction.
485 ``np.dtype(as_ctypes_type(dt))`` will:
487 - insert padding fields
488 - reorder fields to be sorted by offset
489 - discard field titles
491 ``as_ctypes_type(np.dtype(ctype))`` will:
493 - discard the class names of `ctypes.Structure`\ s and
494 `ctypes.Union`\ s
495 - convert single-element `ctypes.Union`\ s into single-element
496 `ctypes.Structure`\ s
497 - insert padding fields
499 """
500 return _ctype_from_dtype(_dtype(dtype))
503 def as_array(obj, shape=None):
504 """
505 Create a numpy array from a ctypes array or POINTER.
507 The numpy array shares the memory with the ctypes object.
509 The shape parameter must be given if converting from a ctypes POINTER.
510 The shape parameter is ignored if converting from a ctypes array
511 """
512 if isinstance(obj, ctypes._Pointer):
513 # convert pointers to an array of the desired shape
514 if shape is None:
515 raise TypeError(
516 'as_array() requires a shape argument when called on a '
517 'pointer')
518 p_arr_type = ctypes.POINTER(_ctype_ndarray(obj._type_, shape))
519 obj = ctypes.cast(obj, p_arr_type).contents
521 return array(obj, copy=False)
524 def as_ctypes(obj):
525 """Create and return a ctypes object from a numpy array. Actually
526 anything that exposes the __array_interface__ is accepted."""
527 ai = obj.__array_interface__
528 if ai["strides"]:
529 raise TypeError("strided arrays not supported")
530 if ai["version"] != 3:
531 raise TypeError("only __array_interface__ version 3 supported")
532 addr, readonly = ai["data"]
533 if readonly:
534 raise TypeError("readonly arrays unsupported")
536 # can't use `_dtype((ai["typestr"], ai["shape"]))` here, as it overflows
537 # dtype.itemsize (gh-14214)
538 ctype_scalar = as_ctypes_type(ai["typestr"])
539 result_type = _ctype_ndarray(ctype_scalar, ai["shape"])
540 result = result_type.from_address(addr)
541 result.__keep = obj
542 return result