This recipe provides an easy-to-use, decorator-based solution to the problem of using functions from other languages, especially C, in Python. It takes advantage of Python's new annotations to provide simple type checking and automatic conversion. If you like it, you may also want to check out my C structure decorator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | import ctypes
class C_function:
"""This class wraps a Python function into its C equivalent in the given library"""
types = ctypes.__dict__
@classmethod
def register_type(cls, typename, obj):
"""Add an acceptable argument or retval type to this."""
cls.types[typename] = obj
def __init__(self, dll, error_check=None):
"""Set the library to reference the function name against."""
self.library = ctypes.CDLL(dll)
self.error_check = error_check
def __call__(self, f):
"""Performs the actual function wrapping.
This extracts function details from annotations and signature.
For example, to wrap puts, the following suffices (on Linux):
>>> @C_function("libc.so.6")
... def puts(my_string: "c_char_p") -> "c_int":
... return puts.c_function(my_string)
...
>>> x = puts("hello, world!")
hello, world!
>>> x
14
Note that to call the actual C function, you just reference
it by asking for the local function's .c_function attribute.
"""
# get the function's name, arguments, and arg types
name = f.__name__
args = f.__code__.co_varnames
annotations = f.__annotations__
# get the function itself
function = self.library[name]
# set the function's call signature
argtypes = []
for arg in args:
annotation = annotations[arg]
if annotation.startswith("*"):
argtypes.append(ctypes.POINTER(self.types[annotation[1:]]))
else:
argtypes.append(self.types[annotation])
function.argtypes = argtypes
# and its return value
restype = annotations['return']
if restype.startswith("*"):
function.restype = ctypes.POINTER(self.types[restype[1:]])
else:
function.restype = self.types[restype]
# set its error handler
if self.error_check:
function.errcheck = self.error_check
# set the c_function as an attribute of f
f.c_function = function
return f
|
How To Use It
Let's say I want to wrap a C function with the following signature
int process_foo(int bar);
from libfoo.so. Using this decorator, I could simply write the following Python:
>>> @C_function("libfoo.so")
... def process_foo(bar: "int") -> "int" :
... return process_foo.c_function(bar)
...
I could then call that just as I would any other Python function:
>>> process_foo(5)
7
It also works with pointers- simply prefix the type with a *- and allows you to add types via C_function.register_type.
That's a really nice snippet!
@david: thanks! I'm working on a similar one for structs/classes using the new class decorators- stay tuned!
To avoid namespace vagaries in looking up "process_foo", a variant might be to make the first argument the C function, or else simply throw the original function away entirely (i.e return "function" from the decorator rather than "f". Nice example of using function annotations effectively, though.