Created
July 4, 2017 23:45
-
-
Save andrewfowlie/dcdcd6a33e376cacf4e61e5d2384427d to your computer and use it in GitHub Desktop.
Load functions into Python from a C++ library automagically
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Load functions from a dynmaic library via the header file | |
========================================================= | |
""" | |
import ctypes | |
import numpy as np | |
import json | |
import sys | |
from collections import OrderedDict as odict | |
from future.utils import viewitems | |
import CppHeaderParser as cparse | |
if sys.version_info[0] < 3: | |
import functools32 as functools | |
else: | |
import functools | |
# Dictionary for string to ctype | |
TYPE_DICT = dict(double=ctypes.c_double, | |
int=ctypes.c_int, | |
bool=ctypes.c_bool) | |
# Decorators for scalar, vectorized and memoized functions | |
def asscalar(func): | |
""" | |
Make function result a scalar, if possible, not a length 0 or 1 array. | |
""" | |
@functools.wraps(func) | |
def rfunc(*args, **kwargs): | |
""" | |
Wrapped function that converts result from array to scalar | |
if result is size == 1 | |
""" | |
res = func(*args, **kwargs) | |
if res.size == 1: | |
res = np.asscalar(res) | |
return res | |
return rfunc | |
decorator = lambda func: asscalar(np.vectorize(functools.lru_cache()(func))) | |
def make_arg_wrapper(name, default): | |
""" | |
:returns: Part of a functions arguments with name = default | |
""" | |
if default: | |
strip = ''.join(default.split()) | |
convert = str(json.loads(strip)) | |
arg = '{}={}'.format(name, convert) | |
else: | |
arg = name | |
return arg | |
def make_wrapper(default): | |
""" | |
:returns: Wrapper for a function that adds default arguments | |
""" | |
wrapped_arg_list = [make_arg_wrapper(*pair) for pair in viewitems(default)] | |
wrapped_arg_str = ', '.join(wrapped_arg_list) | |
arg_str = ', '.join(default.keys()) | |
wrapped_func = 'f_wrapped_gen = lambda f_load: lambda {}: f_load({})'.format(wrapped_arg_str, arg_str) | |
return wrapped_func | |
def libload(lib_name, header_name, decorate=False): | |
""" | |
Load functions from a header file/library. | |
""" | |
header = cparse.CppHeader(header_name) | |
lib = ctypes.CDLL(lib_name) | |
f_dict = dict() | |
for func in header.functions: | |
f_name = func['name'] | |
f_type = func['rtnType'] | |
f_ctype = TYPE_DICT[f_type] | |
p_types = [param['type'] for param in func['parameters']] | |
p_ctypes = [TYPE_DICT[p_type] for p_type in p_types] | |
p_default = odict((param['name'], param.get('default')) for param in func['parameters']) | |
f_load = lib.__getattr__(f_name) | |
f_load.argtypes = p_ctypes | |
f_load.restype = f_ctype | |
f_wrapped_str = make_wrapper(p_default) | |
exec(f_wrapped_str, globals()) # NB that this exec call defines f_wrapped_gen | |
f_wrapped = f_wrapped_gen(f_load) | |
f_decorated = decorator(f_wrapped) if decorate else f_wrapped | |
f_decorated.__name__ = f_name | |
f_decorated.__doc__ = "Python interface to {} loaded from {}".format(f_name, lib_name) | |
f_dict[f_name] = f_decorated | |
return f_dict |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment