_ccallback.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. from . import _ccallback_c
  2. import ctypes
  3. PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
  4. ffi = None
  5. class CData:
  6. pass
  7. def _import_cffi():
  8. global ffi, CData
  9. if ffi is not None:
  10. return
  11. try:
  12. import cffi
  13. ffi = cffi.FFI()
  14. CData = ffi.CData
  15. except ImportError:
  16. ffi = False
  17. class LowLevelCallable(tuple):
  18. """
  19. Low-level callback function.
  20. Some functions in SciPy take as arguments callback functions, which
  21. can either be python callables or low-level compiled functions. Using
  22. compiled callback functions can improve performance somewhat by
  23. avoiding wrapping data in Python objects.
  24. Such low-level functions in SciPy are wrapped in `LowLevelCallable`
  25. objects, which can be constructed from function pointers obtained from
  26. ctypes, cffi, Cython, or contained in Python `PyCapsule` objects.
  27. .. seealso::
  28. Functions accepting low-level callables:
  29. `scipy.integrate.quad`, `scipy.ndimage.generic_filter`,
  30. `scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform`
  31. Usage examples:
  32. :ref:`ndimage-ccallbacks`, :ref:`quad-callbacks`
  33. Parameters
  34. ----------
  35. function : {PyCapsule, ctypes function pointer, cffi function pointer}
  36. Low-level callback function.
  37. user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
  38. User data to pass on to the callback function.
  39. signature : str, optional
  40. Signature of the function. If omitted, determined from *function*,
  41. if possible.
  42. Attributes
  43. ----------
  44. function
  45. Callback function given.
  46. user_data
  47. User data given.
  48. signature
  49. Signature of the function.
  50. Methods
  51. -------
  52. from_cython
  53. Class method for constructing callables from Cython C-exported
  54. functions.
  55. Notes
  56. -----
  57. The argument ``function`` can be one of:
  58. - PyCapsule, whose name contains the C function signature
  59. - ctypes function pointer
  60. - cffi function pointer
  61. The signature of the low-level callback must match one of those expected
  62. by the routine it is passed to.
  63. If constructing low-level functions from a PyCapsule, the name of the
  64. capsule must be the corresponding signature, in the format::
  65. return_type (arg1_type, arg2_type, ...)
  66. For example::
  67. "void (double)"
  68. "double (double, int *, void *)"
  69. The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
  70. if an explicit value for ``user_data`` was not given.
  71. """
  72. # Make the class immutable
  73. __slots__ = ()
  74. def __new__(cls, function, user_data=None, signature=None):
  75. # We need to hold a reference to the function & user data,
  76. # to prevent them going out of scope
  77. item = cls._parse_callback(function, user_data, signature)
  78. return tuple.__new__(cls, (item, function, user_data))
  79. def __repr__(self):
  80. return f"LowLevelCallable({self.function!r}, {self.user_data!r})"
  81. @property
  82. def function(self):
  83. return tuple.__getitem__(self, 1)
  84. @property
  85. def user_data(self):
  86. return tuple.__getitem__(self, 2)
  87. @property
  88. def signature(self):
  89. return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
  90. def __getitem__(self, idx):
  91. raise ValueError()
  92. @classmethod
  93. def from_cython(cls, module, name, user_data=None, signature=None):
  94. """
  95. Create a low-level callback function from an exported Cython function.
  96. Parameters
  97. ----------
  98. module : module
  99. Cython module where the exported function resides
  100. name : str
  101. Name of the exported function
  102. user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
  103. User data to pass on to the callback function.
  104. signature : str, optional
  105. Signature of the function. If omitted, determined from *function*.
  106. """
  107. try:
  108. function = module.__pyx_capi__[name]
  109. except AttributeError as e:
  110. message = "Given module is not a Cython module with __pyx_capi__ attribute"
  111. raise ValueError(message) from e
  112. except KeyError as e:
  113. message = f"No function {name!r} found in __pyx_capi__ of the module"
  114. raise ValueError(message) from e
  115. return cls(function, user_data, signature)
  116. @classmethod
  117. def _parse_callback(cls, obj, user_data=None, signature=None):
  118. _import_cffi()
  119. if isinstance(obj, LowLevelCallable):
  120. func = tuple.__getitem__(obj, 0)
  121. elif isinstance(obj, PyCFuncPtr):
  122. func, signature = _get_ctypes_func(obj, signature)
  123. elif isinstance(obj, CData):
  124. func, signature = _get_cffi_func(obj, signature)
  125. elif _ccallback_c.check_capsule(obj):
  126. func = obj
  127. else:
  128. raise ValueError("Given input is not a callable or a "
  129. "low-level callable (pycapsule/ctypes/cffi)")
  130. if isinstance(user_data, ctypes.c_void_p):
  131. context = _get_ctypes_data(user_data)
  132. elif isinstance(user_data, CData):
  133. context = _get_cffi_data(user_data)
  134. elif user_data is None:
  135. context = 0
  136. elif _ccallback_c.check_capsule(user_data):
  137. context = user_data
  138. else:
  139. raise ValueError("Given user data is not a valid "
  140. "low-level void* pointer (pycapsule/ctypes/cffi)")
  141. return _ccallback_c.get_raw_capsule(func, signature, context)
  142. #
  143. # ctypes helpers
  144. #
  145. def _get_ctypes_func(func, signature=None):
  146. # Get function pointer
  147. func_ptr = ctypes.cast(func, ctypes.c_void_p).value
  148. # Construct function signature
  149. if signature is None:
  150. signature = _typename_from_ctypes(func.restype) + " ("
  151. for j, arg in enumerate(func.argtypes):
  152. if j == 0:
  153. signature += _typename_from_ctypes(arg)
  154. else:
  155. signature += ", " + _typename_from_ctypes(arg)
  156. signature += ")"
  157. return func_ptr, signature
  158. def _typename_from_ctypes(item):
  159. if item is None:
  160. return "void"
  161. elif item is ctypes.c_void_p:
  162. return "void *"
  163. name = item.__name__
  164. pointer_level = 0
  165. while name.startswith("LP_"):
  166. pointer_level += 1
  167. name = name[3:]
  168. if name.startswith('c_'):
  169. name = name[2:]
  170. if pointer_level > 0:
  171. name += " " + "*"*pointer_level
  172. return name
  173. def _get_ctypes_data(data):
  174. # Get voidp pointer
  175. return ctypes.cast(data, ctypes.c_void_p).value
  176. #
  177. # CFFI helpers
  178. #
  179. def _get_cffi_func(func, signature=None):
  180. # Get function pointer
  181. func_ptr = ffi.cast('uintptr_t', func)
  182. # Get signature
  183. if signature is None:
  184. signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
  185. return func_ptr, signature
  186. def _get_cffi_data(data):
  187. # Get pointer
  188. return ffi.cast('uintptr_t', data)