Python 使用kwargs包装接受可选参数结构的函数

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

在C语言中,通常会看到一个函数需要大量的输入,其中许多/大部分是可选的,将这些输入组合到一个结构中,以使开发人员的界面更干净。(即使您应该能够依赖于一个接受没有人真正想要编写那么多的编译器,尤其是在C没有重载或默认函数参数支持的情况下)。作为一个假设的例子,我们可以考虑下面的结构/函数对(test .h)来说明问题:

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
当这样写时,它还有一些更简洁的属性:

  • 它也不会破坏直接使用单结构参数调用函数的语法
  • 它可以支持带有强制位置参数的函数,以及作为最后一个参数的充满可选参数的结构。(尽管目前它不能对强制非结构参数使用kwargs语法)
  • 然后,问题就变成了简单地将该装饰器应用于SWIG生成的Python代码中的正确函数。我的计划是用我能用的最简单的宏来包装它,因为这个模式在我包装的库中是重复的。结果比我想象的要难。(我显然)我最初试过:

    >代码> %特性(“影子”)——我确信它会起作用,而且它确实对C++成员函数起作用,但是对于全局范围的自由函数,它没有用,因为我没有理解的原因。
  • %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)
    
    在真正使用此功能之前,您可能想做的事情:

  • 使用目前编写的这个decorator和kwargs,很难恢复任何类型的类型错误。可能您的C函数有一种指示无效输入组合的方法。将这些转换为Python用户的TypeError异常
  • 根据需要调整宏以支持必需的位置参数
    柔印的装饰非常令人印象深刻。我自己遇到了这个问题,并犹豫提出我的解决方案,除了它有一个可取之处:简单。另外,我的解决方案是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+