In [1]:
import pythran
%load_ext pythran.magic
In [2]:
%%pythran
#pythran export pythran_cbrt(float64(float64), float64)
def pythran_cbrt(libm_cbrt, val):
return libm_cbrt(val)
In that case libm_cbrt is expected to be a capsule containing the function pointer to libm's cbrt (cube root) function.
This capsule can be created using ctypes:
In [3]:
import ctypes
# capsulefactory
PyCapsule_New = ctypes.pythonapi.PyCapsule_New
PyCapsule_New.restype = ctypes.py_object
PyCapsule_New.argtypes = ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p
# load libm
libm = ctypes.CDLL('/lib/x86_64-linux-gnu/libm.so.6')
# extract the proper symbol
cbrt = libm.cbrt
# wrap it
cbrt_capsule = PyCapsule_New(cbrt, "double(double)", None)
The capsule is not usable from Python context (it's some kind of opaque box) but Pythran knows how to use it. beware, it does not try to do any kind of type verification. It trusts your #pythran export line.
In [4]:
pythran_cbrt(cbrt_capsule, 8.)
Out[4]:
In [5]:
%%pythran
#pythran export pythran_sincos(None(float64, float64*, float64*), float64)
def pythran_sincos(libm_sincos, val):
import numpy as np
val_sin, val_cos = np.empty(1), np.empty(1)
libm_sincos(val, val_sin, val_cos)
return val_sin[0], val_cos[0]
There is some magic happening here:
None is used to state the function pointer does not return anything.
In order to create pointers, we actually create empty one-dimensional array and let pythran handle them as pointer. Beware that you're in charge of all the memory checking stuff!
Apart from that, we can now call our function with the proper capsule parameter.
In [6]:
sincos_capsule = PyCapsule_New(libm.sincos, "uncheck any way", None)
In [7]:
pythran_sincos(sincos_capsule, 0.)
Out[7]:
In [8]:
!rm -f cube.c cube.pyx cube.so
In [9]:
%%file cube.pyx
cdef api double cube(double x) nogil:
return x * x * x
Sorry about that, old-school compilation of a Python module, as a one-liner :-)
In a real project, you would use distutils and stuff, of course.
In [10]:
!cythonize cube.pyx
!gcc cube.c -shared -fPIC -O2 -o cube.so `python-config --cflags --libs`
The cythonized module has a special dictionary that holds teh capsule we're looking for.
In [11]:
import cube
print(type(cube.__pyx_capi__['cube']))
In [12]:
cython_cube = cube.__pyx_capi__['cube']
pythran_cbrt(cython_cube, 2.)
Out[12]:
In [13]:
%%pythran
## This is the capsule.
#pythran export capsule corp((int, str), str set)
def corp(param, lookup):
res, key = param
return res if key in lookup else -1
## This is some dummy callsite
#pythran export brief(int, int((int, str), str set)):
def brief(val, capsule):
return capsule((val, "doctor"), {"some"})
It's not possible to call the capsule: it's... an opaque capsule.
In [14]:
try:
corp((1,"some"),set())
except TypeError as e:
print(e)
It's possible to pass it to the according pythran function though.
In [15]:
brief(1, corp)
Out[15]: