如何使用python-cffi包装C函数以支持Python关键字参数调用?
问题解答
CFFI 本身没有内置支持关键字参数调用封装后C函数的功能,默认导出的C函数绑定采用纯位置参数实现,目的是最大化调用性能,没有额外的关键字参数适配逻辑。
如果你不想逐个手写C函数的外层包装,可以通过通用批量包装的方式实现需求,只需要写一次通用逻辑即可适配所有导出的C函数,无需重复编码:
- 第一步先修改
example_extension_build.py中的cdef声明,补全函数的参数名,否则无法建立参数名和位置的映射关系:
ffibuilder.cdef(""" struct my_s{ int a; char * b; }; int my_f(int arg_1, struct my_s arg_2); // 补上参数名 """)
重新编译扩展之后,就可以通过CFFI的类型接口拿到每个函数的参数名信息。
- 第二步在Python导入扩展时加入一次性的通用包装逻辑:
# file: test_example.py import _example as e from _example import lib, ffi def wrap_c_func(c_func): func_type = ffi.typeof(c_func) arg_names = [p.name for p in func_type.args] def wrapper(*args, **kwargs): if not kwargs: return c_func(*args) call_args = list(args) for idx in range(len(args), len(arg_names)): arg_name = arg_names[idx] if arg_name not in kwargs: raise TypeError(f"{c_func.__name__} missing required argument: {arg_name}") call_args.append(kwargs.pop(arg_name)) if kwargs: raise TypeError(f"{c_func.__name__} got unexpected keyword arguments: {list(kwargs.keys())}") return c_func(*call_args) wrapper.__name__ = c_func.__name__ return wrapper # 批量包装所有导出的C函数,不需要逐个处理 for attr_name in dir(lib): attr = getattr(lib, attr_name) if callable(attr): try: if hasattr(ffi.typeof(attr), 'args'): setattr(lib, attr_name, wrap_c_func(attr)) except: pass # 现在已经可以用关键字参数调用了 n = 21 s = e.ffi.new("struct my_s *") s.a = 21 s.b = e.ffi.new("char[]", b"Hello World!") lib.my_f(arg_2=s[0], arg_1=n) # 可以正常运行
这种方案的额外性能开销极低,也不需要你针对每个C函数单独编写包装代码,完全满足优雅实现的需求。
内容的提问来源于stack exchange,提问作者dornhe
相关产品推荐
相关产品推荐

