Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/numpy/ctypeslib.py: 14%
215 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
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', 'c_intp', 'as_ctypes', 'as_array',
53 'as_ctypes_type']
55import os
56from numpy import (
57 integer, ndarray, dtype as _dtype, asarray, frombuffer
58)
59from numpy.core.multiarray import _flagdict, flagsobj
61try:
62 import ctypes
63except ImportError:
64 ctypes = None
66if ctypes is None: 66 ↛ 67line 66 didn't jump to line 67, because the condition on line 66 was never true
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 load_library = _dummy
79 as_ctypes = _dummy
80 as_array = _dummy
81 from numpy import intp as c_intp
82 _ndptr_base = object
83else:
84 import numpy.core._internal as nic
85 c_intp = nic._getintp_ctype()
86 del nic
87 _ndptr_base = ctypes.c_void_p
89 # Adapted from Albert Strasheim
90 def load_library(libname, loader_path):
91 """
92 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 .. versionchanged:: 1.20.0
101 Allow libname and loader_path to take any
102 :term:`python:path-like object`.
104 Parameters
105 ----------
106 libname : path-like
107 Name of the library, which can have 'lib' as a prefix,
108 but without an extension.
109 loader_path : path-like
110 Where the library can be found.
112 Returns
113 -------
114 ctypes.cdll[libpath] : library object
115 A ctypes library object
117 Raises
118 ------
119 OSError
120 If there is no library with the expected extension, or the
121 library is defective and cannot be loaded.
122 """
123 if ctypes.__version__ < '1.0.1':
124 import warnings
125 warnings.warn("All features of ctypes interface may not work "
126 "with ctypes < 1.0.1", stacklevel=2)
128 # Convert path-like objects into strings
129 libname = os.fsdecode(libname)
130 loader_path = os.fsdecode(loader_path)
132 ext = os.path.splitext(libname)[1]
133 if not ext:
134 # Try to load library with platform-specific name, otherwise
135 # default to libname.[so|pyd]. Sometimes, these files are built
136 # erroneously on non-linux platforms.
137 from numpy.distutils.misc_util import get_shared_lib_extension
138 so_ext = get_shared_lib_extension()
139 libname_ext = [libname + so_ext]
140 # mac, windows and linux >= py3.2 shared library and loadable
141 # module have different extensions so try both
142 so_ext2 = get_shared_lib_extension(is_python_ext=True)
143 if not so_ext2 == so_ext:
144 libname_ext.insert(0, libname + so_ext2)
145 else:
146 libname_ext = [libname]
148 loader_path = os.path.abspath(loader_path)
149 if not os.path.isdir(loader_path):
150 libdir = os.path.dirname(loader_path)
151 else:
152 libdir = loader_path
154 for ln in libname_ext:
155 libpath = os.path.join(libdir, ln)
156 if os.path.exists(libpath):
157 try:
158 return ctypes.cdll[libpath]
159 except OSError:
160 ## defective lib file
161 raise
162 ## if no successful return in the libname_ext loop:
163 raise OSError("no file with expected extension")
166def _num_fromflags(flaglist):
167 num = 0
168 for val in flaglist:
169 num += _flagdict[val]
170 return num
172_flagnames = ['C_CONTIGUOUS', 'F_CONTIGUOUS', 'ALIGNED', 'WRITEABLE',
173 'OWNDATA', 'WRITEBACKIFCOPY']
174def _flags_fromnum(num):
175 res = []
176 for key in _flagnames:
177 value = _flagdict[key]
178 if (num & value):
179 res.append(key)
180 return res
183class _ndptr(_ndptr_base):
184 @classmethod
185 def from_param(cls, obj):
186 if not isinstance(obj, ndarray):
187 raise TypeError("argument must be an ndarray")
188 if cls._dtype_ is not None \
189 and obj.dtype != cls._dtype_:
190 raise TypeError("array must have data type %s" % cls._dtype_)
191 if cls._ndim_ is not None \
192 and obj.ndim != cls._ndim_:
193 raise TypeError("array must have %d dimension(s)" % cls._ndim_)
194 if cls._shape_ is not None \
195 and obj.shape != cls._shape_:
196 raise TypeError("array must have shape %s" % str(cls._shape_))
197 if cls._flags_ is not None \
198 and ((obj.flags.num & cls._flags_) != cls._flags_):
199 raise TypeError("array must have flags %s" %
200 _flags_fromnum(cls._flags_))
201 return obj.ctypes
204class _concrete_ndptr(_ndptr):
205 """
206 Like _ndptr, but with `_shape_` and `_dtype_` specified.
208 Notably, this means the pointer has enough information to reconstruct
209 the array, which is not generally true.
210 """
211 def _check_retval_(self):
212 """
213 This method is called when this class is used as the .restype
214 attribute for a shared-library function, to automatically wrap the
215 pointer into an array.
216 """
217 return self.contents
219 @property
220 def contents(self):
221 """
222 Get an ndarray viewing the data pointed to by this pointer.
224 This mirrors the `contents` attribute of a normal ctypes pointer
225 """
226 full_dtype = _dtype((self._dtype_, self._shape_))
227 full_ctype = ctypes.c_char * full_dtype.itemsize
228 buffer = ctypes.cast(self, ctypes.POINTER(full_ctype)).contents
229 return frombuffer(buffer, dtype=full_dtype).squeeze(axis=0)
232# Factory for an array-checking class with from_param defined for
233# use with ctypes argtypes mechanism
234_pointer_type_cache = {}
235def ndpointer(dtype=None, ndim=None, shape=None, flags=None):
236 """
237 Array-checking restype/argtypes.
239 An ndpointer instance is used to describe an ndarray in restypes
240 and argtypes specifications. This approach is more flexible than
241 using, for example, ``POINTER(c_double)``, since several restrictions
242 can be specified, which are verified upon calling the ctypes function.
243 These include data type, number of dimensions, shape and flags. If a
244 given array does not satisfy the specified restrictions,
245 a ``TypeError`` is raised.
247 Parameters
248 ----------
249 dtype : data-type, optional
250 Array data-type.
251 ndim : int, optional
252 Number of array dimensions.
253 shape : tuple of ints, optional
254 Array shape.
255 flags : str or tuple of str
256 Array flags; may be one or more of:
258 - C_CONTIGUOUS / C / CONTIGUOUS
259 - F_CONTIGUOUS / F / FORTRAN
260 - OWNDATA / O
261 - WRITEABLE / W
262 - ALIGNED / A
263 - WRITEBACKIFCOPY / X
265 Returns
266 -------
267 klass : ndpointer type object
268 A type object, which is an ``_ndtpr`` instance containing
269 dtype, ndim, shape and flags information.
271 Raises
272 ------
273 TypeError
274 If a given array does not satisfy the specified restrictions.
276 Examples
277 --------
278 >>> clib.somefunc.argtypes = [np.ctypeslib.ndpointer(dtype=np.float64,
279 ... ndim=1,
280 ... flags='C_CONTIGUOUS')]
281 ... #doctest: +SKIP
282 >>> clib.somefunc(np.array([1, 2, 3], dtype=np.float64))
283 ... #doctest: +SKIP
285 """
287 # normalize dtype to an Optional[dtype]
288 if dtype is not None:
289 dtype = _dtype(dtype)
291 # normalize flags to an Optional[int]
292 num = None
293 if flags is not None:
294 if isinstance(flags, str):
295 flags = flags.split(',')
296 elif isinstance(flags, (int, integer)):
297 num = flags
298 flags = _flags_fromnum(num)
299 elif isinstance(flags, flagsobj):
300 num = flags.num
301 flags = _flags_fromnum(num)
302 if num is None:
303 try:
304 flags = [x.strip().upper() for x in flags]
305 except Exception as e:
306 raise TypeError("invalid flags specification") from e
307 num = _num_fromflags(flags)
309 # normalize shape to an Optional[tuple]
310 if shape is not None:
311 try:
312 shape = tuple(shape)
313 except TypeError:
314 # single integer -> 1-tuple
315 shape = (shape,)
317 cache_key = (dtype, ndim, shape, num)
319 try:
320 return _pointer_type_cache[cache_key]
321 except KeyError:
322 pass
324 # produce a name for the new type
325 if dtype is None:
326 name = 'any'
327 elif dtype.names is not None:
328 name = str(id(dtype))
329 else:
330 name = dtype.str
331 if ndim is not None:
332 name += "_%dd" % ndim
333 if shape is not None:
334 name += "_"+"x".join(str(x) for x in shape)
335 if flags is not None:
336 name += "_"+"_".join(flags)
338 if dtype is not None and shape is not None:
339 base = _concrete_ndptr
340 else:
341 base = _ndptr
343 klass = type("ndpointer_%s"%name, (base,),
344 {"_dtype_": dtype,
345 "_shape_" : shape,
346 "_ndim_" : ndim,
347 "_flags_" : num})
348 _pointer_type_cache[cache_key] = klass
349 return klass
352if ctypes is not None: 352 ↛ exitline 352 didn't exit the module, because the condition on line 352 was never false
353 def _ctype_ndarray(element_type, shape):
354 """ Create an ndarray of the given element type and shape """
355 for dim in shape[::-1]:
356 element_type = dim * element_type
357 # prevent the type name include np.ctypeslib
358 element_type.__module__ = None
359 return element_type
362 def _get_scalar_type_map():
363 """
364 Return a dictionary mapping native endian scalar dtype to ctypes types
365 """
366 ct = ctypes
367 simple_types = [
368 ct.c_byte, ct.c_short, ct.c_int, ct.c_long, ct.c_longlong,
369 ct.c_ubyte, ct.c_ushort, ct.c_uint, ct.c_ulong, ct.c_ulonglong,
370 ct.c_float, ct.c_double,
371 ct.c_bool,
372 ]
373 return {_dtype(ctype): ctype for ctype in simple_types}
376 _scalar_type_map = _get_scalar_type_map()
379 def _ctype_from_dtype_scalar(dtype):
380 # swapping twice ensure that `=` is promoted to <, >, or |
381 dtype_with_endian = dtype.newbyteorder('S').newbyteorder('S')
382 dtype_native = dtype.newbyteorder('=')
383 try:
384 ctype = _scalar_type_map[dtype_native]
385 except KeyError as e:
386 raise NotImplementedError(
387 "Converting {!r} to a ctypes type".format(dtype)
388 ) from None
390 if dtype_with_endian.byteorder == '>':
391 ctype = ctype.__ctype_be__
392 elif dtype_with_endian.byteorder == '<':
393 ctype = ctype.__ctype_le__
395 return ctype
398 def _ctype_from_dtype_subarray(dtype):
399 element_dtype, shape = dtype.subdtype
400 ctype = _ctype_from_dtype(element_dtype)
401 return _ctype_ndarray(ctype, shape)
404 def _ctype_from_dtype_structured(dtype):
405 # extract offsets of each field
406 field_data = []
407 for name in dtype.names:
408 field_dtype, offset = dtype.fields[name][:2]
409 field_data.append((offset, name, _ctype_from_dtype(field_dtype)))
411 # ctypes doesn't care about field order
412 field_data = sorted(field_data, key=lambda f: f[0])
414 if len(field_data) > 1 and all(offset == 0 for offset, name, ctype in field_data):
415 # union, if multiple fields all at address 0
416 size = 0
417 _fields_ = []
418 for offset, name, ctype in field_data:
419 _fields_.append((name, ctype))
420 size = max(size, ctypes.sizeof(ctype))
422 # pad to the right size
423 if dtype.itemsize != size:
424 _fields_.append(('', ctypes.c_char * dtype.itemsize))
426 # we inserted manual padding, so always `_pack_`
427 return type('union', (ctypes.Union,), dict(
428 _fields_=_fields_,
429 _pack_=1,
430 __module__=None,
431 ))
432 else:
433 last_offset = 0
434 _fields_ = []
435 for offset, name, ctype in field_data:
436 padding = offset - last_offset
437 if padding < 0:
438 raise NotImplementedError("Overlapping fields")
439 if padding > 0:
440 _fields_.append(('', ctypes.c_char * padding))
442 _fields_.append((name, ctype))
443 last_offset = offset + ctypes.sizeof(ctype)
446 padding = dtype.itemsize - last_offset
447 if padding > 0:
448 _fields_.append(('', ctypes.c_char * padding))
450 # we inserted manual padding, so always `_pack_`
451 return type('struct', (ctypes.Structure,), dict(
452 _fields_=_fields_,
453 _pack_=1,
454 __module__=None,
455 ))
458 def _ctype_from_dtype(dtype):
459 if dtype.fields is not None:
460 return _ctype_from_dtype_structured(dtype)
461 elif dtype.subdtype is not None:
462 return _ctype_from_dtype_subarray(dtype)
463 else:
464 return _ctype_from_dtype_scalar(dtype)
467 def as_ctypes_type(dtype):
468 r"""
469 Convert a dtype into a ctypes type.
471 Parameters
472 ----------
473 dtype : dtype
474 The dtype to convert
476 Returns
477 -------
478 ctype
479 A ctype scalar, union, array, or struct
481 Raises
482 ------
483 NotImplementedError
484 If the conversion is not possible
486 Notes
487 -----
488 This function does not losslessly round-trip in either direction.
490 ``np.dtype(as_ctypes_type(dt))`` will:
492 - insert padding fields
493 - reorder fields to be sorted by offset
494 - discard field titles
496 ``as_ctypes_type(np.dtype(ctype))`` will:
498 - discard the class names of `ctypes.Structure`\ s and
499 `ctypes.Union`\ s
500 - convert single-element `ctypes.Union`\ s into single-element
501 `ctypes.Structure`\ s
502 - insert padding fields
504 """
505 return _ctype_from_dtype(_dtype(dtype))
508 def as_array(obj, shape=None):
509 """
510 Create a numpy array from a ctypes array or POINTER.
512 The numpy array shares the memory with the ctypes object.
514 The shape parameter must be given if converting from a ctypes POINTER.
515 The shape parameter is ignored if converting from a ctypes array
516 """
517 if isinstance(obj, ctypes._Pointer):
518 # convert pointers to an array of the desired shape
519 if shape is None:
520 raise TypeError(
521 'as_array() requires a shape argument when called on a '
522 'pointer')
523 p_arr_type = ctypes.POINTER(_ctype_ndarray(obj._type_, shape))
524 obj = ctypes.cast(obj, p_arr_type).contents
526 return asarray(obj)
529 def as_ctypes(obj):
530 """Create and return a ctypes object from a numpy array. Actually
531 anything that exposes the __array_interface__ is accepted."""
532 ai = obj.__array_interface__
533 if ai["strides"]:
534 raise TypeError("strided arrays not supported")
535 if ai["version"] != 3:
536 raise TypeError("only __array_interface__ version 3 supported")
537 addr, readonly = ai["data"]
538 if readonly:
539 raise TypeError("readonly arrays unsupported")
541 # can't use `_dtype((ai["typestr"], ai["shape"]))` here, as it overflows
542 # dtype.itemsize (gh-14214)
543 ctype_scalar = as_ctypes_type(ai["typestr"])
544 result_type = _ctype_ndarray(ctype_scalar, ai["shape"])
545 result = result_type.from_address(addr)
546 result.__keep = obj
547 return result