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

1from . import _ccallback_c 

2 

3import ctypes 

4 

5PyCFuncPtr = ctypes.CFUNCTYPE(ctypes.c_void_p).__bases__[0] 

6 

7ffi = None 

8 

9class CData: 

10 pass 

11 

12def _import_cffi(): 

13 global ffi, CData 

14 

15 if ffi is not None: 

16 return 

17 

18 try: 

19 import cffi 

20 ffi = cffi.FFI() 

21 CData = ffi.CData 

22 except ImportError: 

23 ffi = False 

24 

25 

26class LowLevelCallable(tuple): 

27 """ 

28 Low-level callback function. 

29 

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. 

34 

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. 

38 

39 .. seealso:: 

40 

41 Functions accepting low-level callables: 

42 

43 `scipy.integrate.quad`, `scipy.ndimage.generic_filter`, 

44 `scipy.ndimage.generic_filter1d`, `scipy.ndimage.geometric_transform` 

45 

46 Usage examples: 

47 

48 :ref:`ndimage-ccallbacks`, :ref:`quad-callbacks` 

49 

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. 

59 

60 Attributes 

61 ---------- 

62 function 

63 Callback function given. 

64 user_data 

65 User data given. 

66 signature 

67 Signature of the function. 

68 

69 Methods 

70 ------- 

71 from_cython 

72 Class method for constructing callables from Cython C-exported 

73 functions. 

74 

75 Notes 

76 ----- 

77 The argument ``function`` can be one of: 

78 

79 - PyCapsule, whose name contains the C function signature 

80 - ctypes function pointer 

81 - cffi function pointer 

82 

83 The signature of the low-level callback must match one of those expected 

84 by the routine it is passed to. 

85 

86 If constructing low-level functions from a PyCapsule, the name of the 

87 capsule must be the corresponding signature, in the format:: 

88 

89 return_type (arg1_type, arg2_type, ...) 

90 

91 For example:: 

92 

93 "void (double)" 

94 "double (double, int *, void *)" 

95 

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. 

98 

99 """ 

100 

101 # Make the class immutable 

102 __slots__ = () 

103 

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

109 

110 def __repr__(self): 

111 return f"LowLevelCallable({self.function!r}, {self.user_data!r})" 

112 

113 @property 

114 def function(self): 

115 return tuple.__getitem__(self, 1) 

116 

117 @property 

118 def user_data(self): 

119 return tuple.__getitem__(self, 2) 

120 

121 @property 

122 def signature(self): 

123 return _ccallback_c.get_capsule_signature(tuple.__getitem__(self, 0)) 

124 

125 def __getitem__(self, idx): 

126 raise ValueError() 

127 

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. 

132 

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*. 

143 

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) 

154 

155 @classmethod 

156 def _parse_callback(cls, obj, user_data=None, signature=None): 

157 _import_cffi() 

158 

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

170 

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

182 

183 return _ccallback_c.get_raw_capsule(func, signature, context) 

184 

185 

186# 

187# ctypes helpers 

188# 

189 

190def _get_ctypes_func(func, signature=None): 

191 # Get function pointer 

192 func_ptr = ctypes.cast(func, ctypes.c_void_p).value 

193 

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

203 

204 return func_ptr, signature 

205 

206 

207def _typename_from_ctypes(item): 

208 if item is None: 

209 return "void" 

210 elif item is ctypes.c_void_p: 

211 return "void *" 

212 

213 name = item.__name__ 

214 

215 pointer_level = 0 

216 while name.startswith("LP_"): 

217 pointer_level += 1 

218 name = name[3:] 

219 

220 if name.startswith('c_'): 

221 name = name[2:] 

222 

223 if pointer_level > 0: 

224 name += " " + "*"*pointer_level 

225 

226 return name 

227 

228 

229def _get_ctypes_data(data): 

230 # Get voidp pointer 

231 return ctypes.cast(data, ctypes.c_void_p).value 

232 

233 

234# 

235# CFFI helpers 

236# 

237 

238def _get_cffi_func(func, signature=None): 

239 # Get function pointer 

240 func_ptr = ffi.cast('uintptr_t', func) 

241 

242 # Get signature 

243 if signature is None: 

244 signature = ffi.getctype(ffi.typeof(func)).replace('(*)', ' ') 

245 

246 return func_ptr, signature 

247 

248 

249def _get_cffi_data(data): 

250 # Get pointer 

251 return ffi.cast('uintptr_t', data)