| from . import _ccallback_c |
|
|
| import ctypes |
|
|
| PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0] |
|
|
| ffi = None |
|
|
| class CData: |
| pass |
|
|
| def _import_cffi(): |
| global ffi, CData |
|
|
| if ffi is not None: |
| return |
|
|
| try: |
| import cffi |
| ffi = cffi.FFI() |
| CData = ffi.CData |
| except ImportError: |
| ffi = False |
|
|
|
|
| class LowLevelCallable(tuple): |
| """ |
| Low-level callback function. |
| |
| Some functions in SciPy take as arguments callback functions, which |
| can either be python callables or low-level compiled functions. Using |
| compiled callback functions can improve performance somewhat by |
| avoiding wrapping data in Python objects. |
| |
| Such low-level functions in SciPy are wrapped in `LowLevelCallable` |
| objects, which can be constructed from function pointers obtained from |
| ctypes, cffi, Cython, or contained in Python `PyCapsule` objects. |
| |
| .. seealso:: |
| |
| Functions accepting low-level callables: |
| |
| `scipy.integrate.quad`, `scipy.ndimage.generic_filter`, |
| `scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform` |
| |
| Usage examples: |
| |
| :ref:`ndimage-ccallbacks`, :ref:`quad-callbacks` |
| |
| Parameters |
| ---------- |
| function : {PyCapsule, ctypes function pointer, cffi function pointer} |
| Low-level callback function. |
| user_data : {PyCapsule, ctypes void pointer, cffi void pointer} |
| User data to pass on to the callback function. |
| signature : str, optional |
| Signature of the function. If omitted, determined from *function*, |
| if possible. |
| |
| Attributes |
| ---------- |
| function |
| Callback function given. |
| user_data |
| User data given. |
| signature |
| Signature of the function. |
| |
| Methods |
| ------- |
| from_cython |
| Class method for constructing callables from Cython C-exported |
| functions. |
| |
| Notes |
| ----- |
| The argument ``function`` can be one of: |
| |
| - PyCapsule, whose name contains the C function signature |
| - ctypes function pointer |
| - cffi function pointer |
| |
| The signature of the low-level callback must match one of those expected |
| by the routine it is passed to. |
| |
| If constructing low-level functions from a PyCapsule, the name of the |
| capsule must be the corresponding signature, in the format:: |
| |
| return_type (arg1_type, arg2_type, ...) |
| |
| For example:: |
| |
| "void (double)" |
| "double (double, int *, void *)" |
| |
| The context of a PyCapsule passed in as ``function`` is used as ``user_data``, |
| if an explicit value for ``user_data`` was not given. |
| |
| """ |
|
|
| |
| __slots__ = () |
|
|
| def __new__(cls, function, user_data=None, signature=None): |
| |
| |
| item = cls._parse_callback(function, user_data, signature) |
| return tuple.__new__(cls, (item, function, user_data)) |
|
|
| def __repr__(self): |
| return f"LowLevelCallable({self.function!r}, {self.user_data!r})" |
|
|
| @property |
| def function(self): |
| return tuple.__getitem__(self, 1) |
|
|
| @property |
| def user_data(self): |
| return tuple.__getitem__(self, 2) |
|
|
| @property |
| def signature(self): |
| return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0)) |
|
|
| def __getitem__(self, idx): |
| raise ValueError() |
|
|
| @classmethod |
| def from_cython(cls, module, name, user_data=None, signature=None): |
| """ |
| Create a low-level callback function from an exported Cython function. |
| |
| Parameters |
| ---------- |
| module : module |
| Cython module where the exported function resides |
| name : str |
| Name of the exported function |
| user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional |
| User data to pass on to the callback function. |
| signature : str, optional |
| Signature of the function. If omitted, determined from *function*. |
| |
| """ |
| try: |
| function = module.__pyx_capi__[name] |
| except AttributeError as e: |
| message = "Given module is not a Cython module with __pyx_capi__ attribute" |
| raise ValueError(message) from e |
| except KeyError as e: |
| message = f"No function {name!r} found in __pyx_capi__ of the module" |
| raise ValueError(message) from e |
| return cls(function, user_data, signature) |
|
|
| @classmethod |
| def _parse_callback(cls, obj, user_data=None, signature=None): |
| _import_cffi() |
|
|
| if isinstance(obj, LowLevelCallable): |
| func = tuple.__getitem__(obj, 0) |
| elif isinstance(obj, PyCFuncPtr): |
| func, signature = _get_ctypes_func(obj, signature) |
| elif isinstance(obj, CData): |
| func, signature = _get_cffi_func(obj, signature) |
| elif _ccallback_c.check_capsule(obj): |
| func = obj |
| else: |
| raise ValueError("Given input is not a callable or a " |
| "low-level callable (pycapsule/ctypes/cffi)") |
|
|
| if isinstance(user_data, ctypes.c_void_p): |
| context = _get_ctypes_data(user_data) |
| elif isinstance(user_data, CData): |
| context = _get_cffi_data(user_data) |
| elif user_data is None: |
| context = 0 |
| elif _ccallback_c.check_capsule(user_data): |
| context = user_data |
| else: |
| raise ValueError("Given user data is not a valid " |
| "low-level void* pointer (pycapsule/ctypes/cffi)") |
|
|
| return _ccallback_c.get_raw_capsule(func, signature, context) |
|
|
|
|
| |
| |
| |
|
|
| def _get_ctypes_func(func, signature=None): |
| |
| func_ptr = ctypes.cast(func, ctypes.c_void_p).value |
|
|
| |
| if signature is None: |
| signature = _typename_from_ctypes(func.restype) + " (" |
| for j, arg in enumerate(func.argtypes): |
| if j == 0: |
| signature += _typename_from_ctypes(arg) |
| else: |
| signature += ", " + _typename_from_ctypes(arg) |
| signature += ")" |
|
|
| return func_ptr, signature |
|
|
|
|
| def _typename_from_ctypes(item): |
| if item is None: |
| return "void" |
| elif item is ctypes.c_void_p: |
| return "void *" |
|
|
| name = item.__name__ |
|
|
| pointer_level = 0 |
| while name.startswith("LP_"): |
| pointer_level += 1 |
| name = name[3:] |
|
|
| if name.startswith('c_'): |
| name = name[2:] |
|
|
| if pointer_level > 0: |
| name += " " + "*"*pointer_level |
|
|
| return name |
|
|
|
|
| def _get_ctypes_data(data): |
| |
| return ctypes.cast(data, ctypes.c_void_p).value |
|
|
|
|
| |
| |
| |
|
|
| def _get_cffi_func(func, signature=None): |
| |
| func_ptr = ffi.cast('uintptr_t', func) |
|
|
| |
| if signature is None: |
| signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ') |
|
|
| return func_ptr, signature |
|
|
|
|
| def _get_cffi_data(data): |
| |
| return ffi.cast('uintptr_t', data) |
|
|