Python 使用kwargs包装接受可选参数结构的函数
在C语言中,通常会看到一个函数需要大量的输入,其中许多/大部分是可选的,将这些输入组合到一个结构中,以使开发人员的界面更干净。(即使您应该能够依赖于一个接受没有人真正想要编写那么多的编译器,尤其是在C没有重载或默认函数参数支持的情况下)。作为一个假设的例子,我们可以考虑下面的结构/函数对(test .h)来说明问题:Python 使用kwargs包装接受可选参数结构的函数,python,c,swig,Python,C,Swig,在C语言中,通常会看到一个函数需要大量的输入,其中许多/大部分是可选的,将这些输入组合到一个结构中,以使开发人员的界面更干净。(即使您应该能够依赖于一个接受没有人真正想要编写那么多的编译器,尤其是在C没有重载或默认函数参数支持的情况下)。作为一个假设的例子,我们可以考虑下面的结构/函数对(test .h)来说明问题: def StructArgs(ty): def wrap(f): def _wrapper(*args, **kwargs): arg=(ty(),) if
def StructArgs(ty):
def wrap(f):
def _wrapper(*args, **kwargs):
arg=(ty(),) if len(kwargs) else tuple()
for it in kwargs.iteritems():
setattr(arg[0], *it)
return f(*(args+arg))
return _wrapper
return wrap
#包括
类型定义结构{
常量字符*名称;
空虚的东西;
int max_大小;
字符标志;
_鼻涕;
双重共振;
//...
}络合剂;
void ComplexFun(常量ComplexArgs*arg){}
在使用SWIG包装时,我们可以使用以下工具快速完成工作:
%模块测试
%{
#包括“test.h”
%}
typedef boolu bool;
%包括“test.h”
这是可行的,我们可以按如下方式使用它:
导入测试
args=test.ComplexArgs()
args.flags=100;
args.swizzle=True
test.ComplexFun(args)
但这并不完全是蟒蛇式的。Python开发人员更习惯于使用kwargs来支持此类调用:
import test
# Not legal in the interface currently:
test.ComplexFun(flags=100, swizzle=True)
我们怎样才能做到这一点?SWIG-keyword命令行选项也没有帮助,因为函数只有一个实际参数 在Python中,修改函数参数和返回值的方法通常是使用修饰符。作为一个起点,我画出了以下装饰器,它解决了这个问题:
def StructArgs(ty):
def wrap(f):
def _wrapper(*args, **kwargs):
arg=(ty(),) if len(kwargs) else tuple()
for it in kwargs.iteritems():
setattr(arg[0], *it)
return f(*(args+arg))
return _wrapper
return wrap
当这样写时,它还有一些更简洁的属性:
%feature(“autodoc”)
和%feature(“docstring”)
——乐观地说,我希望能够稍微滥用它们,但没有乐趣%pythoncode
就在SWIG看到C端的函数声明之前。生成正确的代码,但不幸的是,SWIG立即隐藏了我们通过添加ComplexFun=\u test.ComplexFun
修饰的函数。很长一段时间都找不到绕过它的方法%rename
隐藏我们调用的实际函数,然后围绕同样经过修饰的实际函数编写包装器。这很管用,但感觉真的很不雅观,因为它基本上使编写上面的decorator变得毫无意义,而不仅仅是在新的包装中编写%pythonprepend
,我可以插入一些内容(任何内容、注释、pass
、空字符串等),这些内容足以抑制阻止#3工作的额外代码
我遇到的最后一个问题是,要使它作为单个宏工作,并正确地获得%pythoncode
指令的位置(还允许%include
对包含声明的头文件进行加密),我必须在%include
之前调用宏。这就需要添加一个额外的%ignore
,以便在实际头文件中第二次看到该函数时忽略该函数。然而,它引入的另一个问题是,我们现在将函数包装在结构之前,因此在Python模块中,当我们调用decorator时,还不知道需要decorator填充的结构类型。通过将字符串(而不是类型)传递给装饰器,然后在
这样一来,完整的工作界面就变成了:
%module test
%pythoncode %{
def StructArgs(type_name):
def wrap(f):
def _wrapper(*args, **kwargs):
ty=globals()[type_name]
arg=(ty(),) if kwargs else tuple()
for it in kwargs.iteritems():
setattr(arg[0], *it)
return f(*(args+arg))
return _wrapper
return wrap
%}
%define %StructArgs(func, ret, type)
%pythoncode %{ @StructArgs(#type) %} // *very* position sensitive
%pythonprepend func %{ %} // Hack to workaround problem with #3
ret func(const type*);
%ignore func;
%enddef
%{
#include "test.h"
%}
typedef bool _Bool;
%StructArgs(ComplexFun, void, ComplexArgs)
%include "test.h"
这就足以使用以下Python代码:
import test
args=test.ComplexArgs()
args.flags=100;
args.swizzle=True
test.ComplexFun(args)
test.ComplexFun(flags=100, swizzle=True)
在真正使用此功能之前,您可能想做的事情:
柔印的装饰非常令人印象深刻。我自己遇到了这个问题,并犹豫提出我的解决方案,除了它有一个可取之处:简单。另外,我的解决方案是C++,但是你可以修改它为C. < /P> 我声明我的OptArgs结构如下:
struct OptArgs {
int oa_a {2},
double oa_b {22.0/7.0};
OptArgs& a(int n) { a = n; return *this; }
OptArgs& b(double n) { b = n; return *this; }
}
调用C++中的构造函数,代码为< > MyClass(RealdJARG,OpTARGSH)(b(2.71))< /C> > 现在,我在.I文件中使用以下命令来移动SWIG生成的构造函数,并解压缩关键字参数:
%include "myclass.h"
%extend MyClass {
%pythoncode %{
SWIG__init__ = __init__
def __init__(self, *args, **kwargs):
if len(kwargs) != 0:
optargs = OptArgs()
for arg in kwargs:
set_method = getattr(optargs, arg, None)
# Deliberately let an error happen here if the argument is bogus
set_method(kwargs[arg])
args += (optargs,)
MyClass.SWIG__init__(self, *args)
%}
};
它不是完美的:它依赖于SWIG生成的\uuuu init\uuuu
声明后发生的扩展,并且是特定于python的,但似乎工作正常,非常非常简单
我希望这会有帮助。我喜欢这个设计——如果是prod代码,我可能会让它引发一个更有用的异常,但除此之外,它真的很整洁!谢谢,@Flexo!如果
set\u method
为None,则生产代码将使用有问题的kwarg名称引发TypeError。还有一个与ARG的妥协。我们的C+