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

1""" 

2============================ 

3``ctypes`` Utility Functions 

4============================ 

5 

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. 

12 

13References 

14---------- 

15.. [1] "SciPy Cookbook: ctypes", https://scipy-cookbook.readthedocs.io/items/Ctypes.html 

16 

17Examples 

18-------- 

19Load the C library: 

20 

21>>> _lib = np.ctypeslib.load_library('libmystuff', '.') #doctest: +SKIP 

22 

23Our result type, an ndarray that must be of type double, be 1-dimensional 

24and is C-contiguous in memory: 

25 

26>>> array_1d_double = np.ctypeslib.ndpointer( 

27... dtype=np.double, 

28... ndim=1, flags='CONTIGUOUS') #doctest: +SKIP 

29 

30Our C-function typically takes an array and updates its values 

31in-place. For example:: 

32 

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 } 

40 

41We wrap it using: 

42 

43>>> _lib.foo_func.restype = None #doctest: +SKIP 

44>>> _lib.foo_func.argtypes = [array_1d_double, c_int] #doctest: +SKIP 

45 

46Then, we're ready to call ``foo_func``: 

47 

48>>> out = np.empty(15, dtype=np.double) 

49>>> _lib.foo_func(out, len(out)) #doctest: +SKIP 

50 

51""" 

52__all__ = ['load_library', 'ndpointer', 'c_intp', 'as_ctypes', 'as_array', 

53 'as_ctypes_type'] 

54 

55import os 

56from numpy import ( 

57 integer, ndarray, dtype as _dtype, asarray, frombuffer 

58) 

59from numpy.core.multiarray import _flagdict, flagsobj 

60 

61try: 

62 import ctypes 

63except ImportError: 

64 ctypes = None 

65 

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. 

70 

71 Raises 

72 ------ 

73 ImportError 

74 If ctypes is not available. 

75 

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 

88 

89 # Adapted from Albert Strasheim 

90 def load_library(libname, loader_path): 

91 """ 

92 It is possible to load a library using 

93 

94 >>> lib = ctypes.cdll[<full_path_name>] # doctest: +SKIP 

95 

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. 

99 

100 .. versionchanged:: 1.20.0 

101 Allow libname and loader_path to take any 

102 :term:`python:path-like object`. 

103 

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. 

111 

112 Returns 

113 ------- 

114 ctypes.cdll[libpath] : library object 

115 A ctypes library object 

116 

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) 

127 

128 # Convert path-like objects into strings 

129 libname = os.fsdecode(libname) 

130 loader_path = os.fsdecode(loader_path) 

131 

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] 

147 

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 

153 

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") 

164 

165 

166def _num_fromflags(flaglist): 

167 num = 0 

168 for val in flaglist: 

169 num += _flagdict[val] 

170 return num 

171 

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 

181 

182 

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 

202 

203 

204class _concrete_ndptr(_ndptr): 

205 """ 

206 Like _ndptr, but with `_shape_` and `_dtype_` specified. 

207 

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 

218 

219 @property 

220 def contents(self): 

221 """ 

222 Get an ndarray viewing the data pointed to by this pointer. 

223 

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) 

230 

231 

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. 

238 

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. 

246 

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: 

257 

258 - C_CONTIGUOUS / C / CONTIGUOUS 

259 - F_CONTIGUOUS / F / FORTRAN 

260 - OWNDATA / O 

261 - WRITEABLE / W 

262 - ALIGNED / A 

263 - WRITEBACKIFCOPY / X 

264 

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. 

270 

271 Raises 

272 ------ 

273 TypeError 

274 If a given array does not satisfy the specified restrictions. 

275 

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 

284 

285 """ 

286 

287 # normalize dtype to an Optional[dtype] 

288 if dtype is not None: 

289 dtype = _dtype(dtype) 

290 

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) 

308 

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,) 

316 

317 cache_key = (dtype, ndim, shape, num) 

318 

319 try: 

320 return _pointer_type_cache[cache_key] 

321 except KeyError: 

322 pass 

323 

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) 

337 

338 if dtype is not None and shape is not None: 

339 base = _concrete_ndptr 

340 else: 

341 base = _ndptr 

342 

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 

350 

351 

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 

360 

361 

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} 

374 

375 

376 _scalar_type_map = _get_scalar_type_map() 

377 

378 

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 

389 

390 if dtype_with_endian.byteorder == '>': 

391 ctype = ctype.__ctype_be__ 

392 elif dtype_with_endian.byteorder == '<': 

393 ctype = ctype.__ctype_le__ 

394 

395 return ctype 

396 

397 

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) 

402 

403 

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))) 

410 

411 # ctypes doesn't care about field order 

412 field_data = sorted(field_data, key=lambda f: f[0]) 

413 

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)) 

421 

422 # pad to the right size 

423 if dtype.itemsize != size: 

424 _fields_.append(('', ctypes.c_char * dtype.itemsize)) 

425 

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)) 

441 

442 _fields_.append((name, ctype)) 

443 last_offset = offset + ctypes.sizeof(ctype) 

444 

445 

446 padding = dtype.itemsize - last_offset 

447 if padding > 0: 

448 _fields_.append(('', ctypes.c_char * padding)) 

449 

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 )) 

456 

457 

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) 

465 

466 

467 def as_ctypes_type(dtype): 

468 r""" 

469 Convert a dtype into a ctypes type. 

470 

471 Parameters 

472 ---------- 

473 dtype : dtype 

474 The dtype to convert 

475 

476 Returns 

477 ------- 

478 ctype 

479 A ctype scalar, union, array, or struct 

480 

481 Raises 

482 ------ 

483 NotImplementedError 

484 If the conversion is not possible 

485 

486 Notes 

487 ----- 

488 This function does not losslessly round-trip in either direction. 

489 

490 ``np.dtype(as_ctypes_type(dt))`` will: 

491 

492 - insert padding fields 

493 - reorder fields to be sorted by offset 

494 - discard field titles 

495 

496 ``as_ctypes_type(np.dtype(ctype))`` will: 

497 

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 

503 

504 """ 

505 return _ctype_from_dtype(_dtype(dtype)) 

506 

507 

508 def as_array(obj, shape=None): 

509 """ 

510 Create a numpy array from a ctypes array or POINTER. 

511 

512 The numpy array shares the memory with the ctypes object. 

513 

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 

525 

526 return asarray(obj) 

527 

528 

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") 

540 

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