Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/_lib/_ccallback.py: 27%
100 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 06:39 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-03 06:39 +0000
1from . import _ccallback_c
3import ctypes
5PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0]
7ffi = None
9class CData:
10 pass
12def _import_cffi():
13 global ffi, CData
15 if ffi is not None:
16 return
18 try:
19 import cffi
20 ffi = cffi.FFI()
21 CData = ffi.CData
22 except ImportError:
23 ffi = False
26class LowLevelCallable(tuple):
27 """
28 Low-level callback function.
30 Some functions in SciPy take as arguments callback functions, which
31 can either be python callables or low-level compiled functions. Using
32 compiled callback functions can improve performance somewhat by
33 avoiding wrapping data in Python objects.
35 Such low-level functions in SciPy are wrapped in `LowLevelCallable`
36 objects, which can be constructed from function pointers obtained from
37 ctypes, cffi, Cython, or contained in Python `PyCapsule` objects.
39 .. seealso::
41 Functions accepting low-level callables:
43 `scipy.integrate.quad`, `scipy.ndimage.generic_filter`,
44 `scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform`
46 Usage examples:
48 :ref:`ndimage-ccallbacks`, :ref:`quad-callbacks`
50 Parameters
51 ----------
52 function : {PyCapsule, ctypes function pointer, cffi function pointer}
53 Low-level callback function.
54 user_data : {PyCapsule, ctypes void pointer, cffi void pointer}
55 User data to pass on to the callback function.
56 signature : str, optional
57 Signature of the function. If omitted, determined from *function*,
58 if possible.
60 Attributes
61 ----------
62 function
63 Callback function given.
64 user_data
65 User data given.
66 signature
67 Signature of the function.
69 Methods
70 -------
71 from_cython
72 Class method for constructing callables from Cython C-exported
73 functions.
75 Notes
76 -----
77 The argument ``function`` can be one of:
79 - PyCapsule, whose name contains the C function signature
80 - ctypes function pointer
81 - cffi function pointer
83 The signature of the low-level callback must match one of those expected
84 by the routine it is passed to.
86 If constructing low-level functions from a PyCapsule, the name of the
87 capsule must be the corresponding signature, in the format::
89 return_type (arg1_type, arg2_type, ...)
91 For example::
93 "void (double)"
94 "double (double, int *, void *)"
96 The context of a PyCapsule passed in as ``function`` is used as ``user_data``,
97 if an explicit value for ``user_data`` was not given.
99 """
101 # Make the class immutable
102 __slots__ = ()
104 def __new__(cls, function, user_data=None, signature=None):
105 # We need to hold a reference to the function & user data,
106 # to prevent them going out of scope
107 item = cls._parse_callback(function, user_data, signature)
108 return tuple.__new__(cls, (item, function, user_data))
110 def __repr__(self):
111 return f"LowLevelCallable({self.function!r}, {self.user_data!r})"
113 @property
114 def function(self):
115 return tuple.__getitem__(self, 1)
117 @property
118 def user_data(self):
119 return tuple.__getitem__(self, 2)
121 @property
122 def signature(self):
123 return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0))
125 def __getitem__(self, idx):
126 raise ValueError()
128 @classmethod
129 def from_cython(cls, module, name, user_data=None, signature=None):
130 """
131 Create a low-level callback function from an exported Cython function.
133 Parameters
134 ----------
135 module : module
136 Cython module where the exported function resides
137 name : str
138 Name of the exported function
139 user_data : {PyCapsule, ctypes void pointer, cffi void pointer}, optional
140 User data to pass on to the callback function.
141 signature : str, optional
142 Signature of the function. If omitted, determined from *function*.
144 """
145 try:
146 function = module.__pyx_capi__[name]
147 except AttributeError as e:
148 message = "Given module is not a Cython module with __pyx_capi__ attribute"
149 raise ValueError(message) from e
150 except KeyError as e:
151 message = f"No function {name!r} found in __pyx_capi__ of the module"
152 raise ValueError(message) from e
153 return cls(function, user_data, signature)
155 @classmethod
156 def _parse_callback(cls, obj, user_data=None, signature=None):
157 _import_cffi()
159 if isinstance(obj, LowLevelCallable):
160 func = tuple.__getitem__(obj, 0)
161 elif isinstance(obj, PyCFuncPtr):
162 func, signature = _get_ctypes_func(obj, signature)
163 elif isinstance(obj, CData):
164 func, signature = _get_cffi_func(obj, signature)
165 elif _ccallback_c.check_capsule(obj):
166 func = obj
167 else:
168 raise ValueError("Given input is not a callable or a "
169 "low-level callable (pycapsule/ctypes/cffi)")
171 if isinstance(user_data, ctypes.c_void_p):
172 context = _get_ctypes_data(user_data)
173 elif isinstance(user_data, CData):
174 context = _get_cffi_data(user_data)
175 elif user_data is None:
176 context = 0
177 elif _ccallback_c.check_capsule(user_data):
178 context = user_data
179 else:
180 raise ValueError("Given user data is not a valid "
181 "low-level void* pointer (pycapsule/ctypes/cffi)")
183 return _ccallback_c.get_raw_capsule(func, signature, context)
186#
187# ctypes helpers
188#
190def _get_ctypes_func(func, signature=None):
191 # Get function pointer
192 func_ptr = ctypes.cast(func, ctypes.c_void_p).value
194 # Construct function signature
195 if signature is None:
196 signature = _typename_from_ctypes(func.restype) + " ("
197 for j, arg in enumerate(func.argtypes):
198 if j == 0:
199 signature += _typename_from_ctypes(arg)
200 else:
201 signature += ", " + _typename_from_ctypes(arg)
202 signature += ")"
204 return func_ptr, signature
207def _typename_from_ctypes(item):
208 if item is None:
209 return "void"
210 elif item is ctypes.c_void_p:
211 return "void *"
213 name = item.__name__
215 pointer_level = 0
216 while name.startswith("LP_"):
217 pointer_level += 1
218 name = name[3:]
220 if name.startswith('c_'):
221 name = name[2:]
223 if pointer_level > 0:
224 name += " " + "*"*pointer_level
226 return name
229def _get_ctypes_data(data):
230 # Get voidp pointer
231 return ctypes.cast(data, ctypes.c_void_p).value
234#
235# CFFI helpers
236#
238def _get_cffi_func(func, signature=None):
239 # Get function pointer
240 func_ptr = ffi.cast('uintptr_t', func)
242 # Get signature
243 if signature is None:
244 signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ')
246 return func_ptr, signature
249def _get_cffi_data(data):
250 # Get pointer
251 return ffi.cast('uintptr_t', data)