ActiveState Code

Recipe 576731: C function decorator


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

Python
 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

Discussion

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.

Comments

  1. 1. At 6:15 a.m. on 28 apr 2009, david.gaarenstroom said:

    That's a really nice snippet!

  2. 2. At 1:58 p.m. on 28 apr 2009, geremy condra (the author) said:

    @david: thanks! I'm working on a similar one for structs/classes using the new class decorators- stay tuned!

Sign in to comment