使用kwargs包装一个带有可选参数结构的函数

问题描述 投票:2回答:2

在C中,看到一个需要大量输入的函数并不罕见,其中许多/大多数都是可选组,这些在结构中使得开发人员的界面更加清晰。 (即使你应该能够依赖编译器接受at least 127 arguments to a function,但实际上没有人愿意写那么多,特别是因为C没有重载或默认函数参数支持)。作为一个假设的例子,我们可以考虑以下结构/函数对(test.h)来说明问题:

#include <stdbool.h>

typedef struct {
  const char *name;
  void *stuff;
  int max_size;
  char flags;
  _Bool swizzle;
  double frobination;
  //...
} ComplexArgs;

void ComplexFun(const ComplexArgs *arg) {}

当使用SWIG进行包装时,我们可以使用以下方法快速完成工作:

%module test

%{
#include "test.h"
%}

typedef bool _Bool;

%include "test.h"

这有效,我们可以使用如下:

import test

args=test.ComplexArgs()
args.flags=100;
args.swizzle=True
test.ComplexFun(args)

但这并不完全是Pythonic。 Python开发人员会更习惯于看到用于支持这种调用的kwargs:

import test

# Not legal in the interface currently:
test.ComplexFun(flags=100, swizzle=True)

我们怎样才能做到这一点? SWIG -keyword命令行选项也没有帮助,因为该函数只有一个实际参数。

python c swig
2个回答
5
投票

通常在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

如下所示,它有一些更整洁的属性:

  1. 它也不会破坏使用单个struct参数直接调用函数的语法
  2. 它可以支持具有强制位置参数的函数和一个充满可选参数的结构作为最后一个参数。 (虽然目前不能将kwargs语法用于强制非结构参数)

然后问题变成简单地将装饰器应用于SWIG生成的Python代码中的正确函数。我的计划是用尽可能简单的宏来包装它,因为模式在库中重复我正在包装很多。事实证明这比我预期的要难。 (我显然是not the only one)我最初尝试过:

  1. %feature("shadow") - 我很确定它会起作用,事实上它确实适用于C ++成员函数,但由于某些我无法理解的原因,它不适用于全局范围内的自由函数。
  2. %feature("autodoc")%feature("docstring") - 乐观地说,我希望能够轻微地滥用它们,但没有喜悦
  3. 在SWIG看到C侧的功能声明之前,%pythoncode。生成正确的代码,但不幸的是,SWIG立即通过添加ComplexFun = _test.ComplexFun来隐藏我们装饰的功能。很长一段时间无法找到解决方法。
  4. 使用%rename来隐藏我们调用的真实函数,然后在真实函数周围编写一个包装器,它也被装饰。这很有效,但感觉非常不优雅,因为它基本上使得编写上面的装饰器毫无意义,而不是仅仅在新的包装器中编写它。

最后,我发现了一个更简洁的技巧来装饰自由功能。通过在函数上使用%pythonprepend,我可以插入一些东西(任何东西,一个注释,pass,空字符串等),足以抑制阻止#3工作的额外代码。

我遇到的最后一个问题是,使它全部作为单个宏工作并获得%pythoncode指令的位置正确(还允许%includeing包含声明的头文件)我必须在%include之前调用宏。这需要添加一个额外的%ignore来忽略该函数if / when它在实际的头文件中第二次被看到。然而,它引入的另一个问题是我们现在将函数包装在struct之前,因此在Python模块中我们需要装饰器填充的结构类型在我们调用装饰器时尚不可知。通过将字符串传递给装饰器而不是类型并稍后在module globals()中查找它,可以很容易地修复。

所以说,包装它的完整的工作界面变为:

%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)

在真正使用它之前你可能想要做的事情:

  1. 有了这个装饰器和当前编写的kwargs,很难得到任何类型的TypeError。可能你的C函数有一种指示无效输入组合的方法。将这些转换为Python用户的TypeError异常。
  2. 如果需要,调整宏以支持强制位置参数。

1
投票

Flexo的装饰非常令人印象深刻。我为自己遇到了这个问题,并犹豫提出我的解决方案,除了它有一个优点:简单。此外,我的解决方案是针对C ++,但您可以为C修改它。

我声明我的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; }
}

例如,用MyClass(required_arg, OptArgs().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生成的__init__声明之后发生的扩展,并且是特定于python的,但似乎工作正常并且非常非常简单。

我希望这很有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.