Welcome, guest | Sign In | My Account | Store | Cart

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, 69 lines
 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.

3 comments

david.gaarenstroom 14 years, 12 months ago  # | flag

That's a really nice snippet!

geremy condra (author) 14 years, 12 months ago  # | flag

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

Nick Coghlan 13 years ago  # | flag

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.

Created by geremy condra on Sun, 26 Apr 2009 (MIT)
Python recipes (4591)
geremy condra's recipes (8)

Required Modules

Other Information and Tasks