1""" 2Convert Python functions to PARI closures 3***************************************** 4 5AUTHORS: 6 7- Jeroen Demeyer (2015-04-10): initial version, :trac:`18052`. 8 9Examples: 10 11>>> def the_answer(): 12... return 42 13>>> import cypari2 14>>> pari = cypari2.Pari() 15>>> f = pari(the_answer) 16>>> f() 1742 18 19>>> cube = pari(lambda i: i**3) 20>>> cube.apply(range(10)) 21[0, 1, 8, 27, 64, 125, 216, 343, 512, 729] 22""" 23 24#***************************************************************************** 25# Copyright (C) 2015 Jeroen Demeyer <jdemeyer@cage.ugent.be> 26# 27# This program is free software: you can redistribute it and/or modify 28# it under the terms of the GNU General Public License as published by 29# the Free Software Foundation, either version 2 of the License, or 30# (at your option) any later version. 31# http://www.gnu.org/licenses/ 32#***************************************************************************** 33 34from __future__ import absolute_import, division, print_function 35 36from cysignals.signals cimport sig_on, sig_off, sig_block, sig_unblock, sig_error 37 38from cpython.tuple cimport * 39from cpython.object cimport PyObject_Call 40from cpython.ref cimport Py_INCREF 41 42from .paridecl cimport * 43from .stack cimport new_gen, new_gen_noclear, clone_gen_noclear, DetachGen 44from .gen cimport objtogen 45 46try: 47 from inspect import getfullargspec as getargspec 48except ImportError: 49 from inspect import getargspec 50 51 52cdef inline GEN call_python_func_impl "call_python_func"(GEN* args, object py_func) except NULL: 53 """ 54 Call ``py_func(*args)`` where ``py_func`` is a Python function 55 and ``args`` is an array of ``GEN``s terminated by ``NULL``. 56 57 The arguments are converted from ``GEN`` to a cypari ``gen`` before 58 calling ``py_func``. The result is converted back to a PARI ``GEN``. 59 """ 60 # We need to ensure that nothing above avma is touched 61 avmaguard = new_gen_noclear(<GEN>avma) 62 63 # How many arguments are there? 64 cdef Py_ssize_t n = 0 65 while args[n] is not NULL: 66 n += 1 67 68 # Construct a Python tuple for args 69 cdef tuple t = PyTuple_New(n) 70 cdef Py_ssize_t i 71 for i in range(n): 72 a = clone_gen_noclear(args[i]) 73 Py_INCREF(a) # Need to increase refcount because the tuple steals it 74 PyTuple_SET_ITEM(t, i, a) 75 76 # Call the Python function 77 r = PyObject_Call(py_func, t, <dict>NULL) 78 79 # Convert the result to a GEN and copy it to the PARI stack 80 # (with a special case for None) 81 if r is None: 82 return gnil 83 84 # Safely delete r and avmaguard 85 d = DetachGen(objtogen(r)) 86 del r 87 res = d.detach() 88 d = DetachGen(avmaguard) 89 del avmaguard 90 d.detach() 91 92 return res 93 94 95# We rename this function to be able to call it with a different 96# signature. In particular, we want manual exception handling and we 97# implicitly convert py_func from a PyObject* to an object. 98cdef extern from *: 99 GEN call_python_func(GEN* args, PyObject* py_func) 100 101 102cdef GEN call_python(GEN arg1, GEN arg2, GEN arg3, GEN arg4, GEN arg5, ulong nargs, ulong py_func): 103 """ 104 This function, which will be installed in PARI, is a front-end for 105 ``call_python_func_impl``. 106 107 It has 5 optional ``GEN``s as argument, a ``nargs`` argument 108 specifying how many arguments are valid and one ``ulong``, which is 109 actually a Python callable object cast to ``ulong``. 110 """ 111 if nargs > 5: 112 sig_error() 113 114 # Convert arguments to a NULL-terminated array. 115 cdef GEN args[6] 116 args[0] = arg1 117 args[1] = arg2 118 args[2] = arg3 119 args[3] = arg4 120 args[4] = arg5 121 args[nargs] = NULL 122 123 sig_block() 124 # Disallow interrupts during the Python code inside 125 # call_python_func_impl(). We need to do this because this function 126 # is very likely called within sig_on() and interrupting arbitrary 127 # Python code is bad. 128 cdef GEN r = call_python_func(args, <PyObject*>py_func) 129 sig_unblock() 130 if not r: # An exception was raised 131 sig_error() 132 return r 133 134 135# Install the function "call_python" for use in the PARI library. 136cdef entree* ep_call_python 137 138cdef int _pari_init_closure() except -1: 139 sig_on() 140 global ep_call_python 141 ep_call_python = install(<void*>call_python, "call_python", 'DGDGDGDGDGD5,U,U') 142 sig_off() 143 144 145cpdef Gen objtoclosure(f): 146 """ 147 Convert a Python function (more generally, any callable) to a PARI 148 ``t_CLOSURE``. 149 150 .. NOTE:: 151 152 With the current implementation, the function can be called 153 with at most 5 arguments. 154 155 .. WARNING:: 156 157 The function ``f`` which is called through the closure cannot 158 be interrupted. Therefore, it is advised to use this only for 159 simple functions which do not take a long time. 160 161 Examples: 162 163 >>> from cypari2.closure import objtoclosure 164 >>> def pymul(i,j): return i*j 165 >>> mul = objtoclosure(pymul) 166 >>> mul 167 (v1,v2)->call_python(v1,v2,0,0,0,2,...) 168 >>> mul(6,9) 169 54 170 >>> mul.type() 171 't_CLOSURE' 172 >>> mul.arity() 173 2 174 >>> def printme(x): 175 ... print(x) 176 >>> objtoclosure(printme)('matid(2)') 177 [1, 0; 0, 1] 178 179 Construct the Riemann zeta function using a closure: 180 181 >>> from cypari2 import Pari; pari = Pari() 182 >>> def coeffs(n): 183 ... return [1 for i in range(n)] 184 >>> Z = pari.lfuncreate([coeffs, 0, [0], 1, 1, 1, 1]) 185 >>> Z.lfun(2) 186 1.64493406684823 187 188 A trivial closure: 189 190 >>> f = pari(lambda x: x) 191 >>> f(10) 192 10 193 194 Test various kinds of errors: 195 196 >>> mul(4) 197 Traceback (most recent call last): 198 ... 199 TypeError: pymul() ... 200 >>> mul(None, None) 201 Traceback (most recent call last): 202 ... 203 ValueError: Cannot convert None to pari 204 >>> mul(*range(100)) 205 Traceback (most recent call last): 206 ... 207 PariError: call_python: too many parameters in user-defined function call 208 >>> mul([1], [2]) 209 Traceback (most recent call last): 210 ... 211 PariError: call_python: forbidden multiplication t_VEC (1 elts) * t_VEC (1 elts) 212 """ 213 if not callable(f): 214 raise TypeError("argument to objtoclosure() must be callable") 215 216 # Determine number of arguments of f 217 cdef Py_ssize_t i, nargs 218 try: 219 argspec = getargspec(f) 220 except Exception: 221 nargs = 5 222 else: 223 nargs = len(argspec.args) 224 225 # Only 5 arguments are supported for now... 226 if nargs > 5: 227 nargs = 5 228 229 # Fill in default arguments of PARI function 230 sig_on() 231 cdef GEN args = cgetg((5 - nargs) + 2 + 1, t_VEC) 232 for i in range(5 - nargs): 233 set_gel(args, i + 1, gnil) 234 set_gel(args, (5 - nargs) + 1, stoi(nargs)) 235 # Convert f to a t_INT containing the address of f 236 set_gel(args, (5 - nargs) + 1 + 1, utoi(<ulong><PyObject*>f)) 237 238 # Create a t_CLOSURE which calls call_python() with py_func equal to f 239 cdef Gen res = new_gen(snm_closure(ep_call_python, args)) 240 241 # We need to keep a reference to f somewhere and there is no way to 242 # have PARI handle this reference for us. So the only way out is to 243 # force f to be never deallocated 244 Py_INCREF(f) 245 246 return res 247