简体   繁体   English

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

[英]Wrap a function that takes a struct of optional arguments using kwargs

In C it's not uncommon to see a function that takes a lot of inputs, many/most of which are optional group these up in a struct to make the interface cleaner for developers. 在C语言中,经常会看到需要大量输入的函数,其中许多/大多数是可选的,将这些组合在一个结构中以使开发人员的界面更整洁。 (Even though you should be able to rely on a compiler accepting at least 127 arguments to a function nobody actually wants to write that many, especially as C has no overloading or default function argument support). (即使您应该能够依赖于接受至少127个函数参数的编译器实际上也没有人愿意编写那么多参数,特别是因为C没有重载或默认函数参数支持)。 As a hypothetical example we could consider the following struct/function pair (test.h) to illustrate the problem: 作为一个假设示例,我们可以考虑使用以下结构/函数对(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) {}

When it comes to wrapping this using SWIG we can get something working quickly using: 在使用SWIG进行包装时,我们可以使用以下方法快速完成工作:

%module test

%{
#include "test.h"
%}

typedef bool _Bool;

%include "test.h"

That works and we can use it as follows: 那行得通,我们可以如下使用它:

import test

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

But that isn't exactly Pythonic. 但这不完全是Pythonic。 A Python developer would be more accustomed to seeing kwargs used to support this kind of calling: Python开发人员会更习惯于使用kwarg支持这种调用:

import test

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

How can we make that work? 我们如何使这项工作? The SWIG -keyword command line option doesn't help either because there's only one actual argument to the function. SWIG -keyword命令行选项也无济于事,因为该函数只有一个实际参数。

Normally in Python the way to modify function arguments and return values is to use a decorator. 通常在Python中,修改函数参数和返回值的方法是使用装饰器。 As a starting point I sketched out the following decorator, which solves the problem: 首先,我草绘了以下装饰器,它解决了这个问题:

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

It has some further neat properties when written like that: 这样编写时,它还具有一些更简洁的属性:

  1. It also doesn't break the syntax for calling the function directly with the single struct argument 它也不会破坏使用单个struct参数直接调用函数的语法
  2. It can support functions with mandatory positional arguments and a struct full of optional arguments as the last argument. 它可以支持带有强制位置参数作为最后一个参数的可选参数的结构的函数。 (Although it can't use kwargs syntax for mandatory non-struct arguments currently) (尽管当前不能将kwargs语法用于必需的非结构参数)

The question then becomes one of simply applying that decorator to the right function inside the SWIG generated Python code. 然后,问题就变成了简单地将装饰器应用于SWIG生成的Python代码中的正确函数之一。 My plan was to wrap it up in the simplest possible macro I could, because the pattern is repeated across the library I'm wrapping lots. 我的计划是将其包装在我能使用的最简单的宏中,因为在包装很多的库中重复了该模式。 That turned out to be harder than I expected though. 事实证明,这比我想象的要难。 (And I'm apparently not the only one ) I initially tried: (而且我显然不是唯一的一个 )我最初尝试过:

  1. %feature("shadow") - I was pretty sure that would work, and indeed it does work for C++ member functions, but it doesn't work for free functions at global scope for some reason I didn't figure out. %feature("shadow") -我非常确定这可以工作,并且确实适用于C ++成员函数,但是由于我不知道的原因,它不适用于全局范围内的自由函数。
  2. %feature("autodoc") and %feature("docstring") - optimistically I'd hoped to be able to abuse them slightly, but no joy %feature("autodoc")%feature("docstring") -乐观地我希望能够略微滥用它们,但不要高兴
  3. %pythoncode right before the SWIG sees the function declaration on the C side. 在SWIG在C端看到函数声明之前的%pythoncode Generates the right code, but unfortunately SWIG immediately hides the function we decorated by adding ComplexFun = _test.ComplexFun . 生成正确的代码,但是不幸的是,SWIG通过添加ComplexFun = _test.ComplexFun立即隐藏了我们装饰的函数。 Couldn't find a way around it for quite a while. 很长一段时间都找不到解决方法。
  4. Use %rename to hide the real function we call and then write a wrapper around the real function which was also decorated. 使用%rename隐藏我们调用的真实函数,然后围绕同样经过修饰的真实函数编写包装。 That worked, but felt really inelegant, because it basically made writing the above decorator pointless instead of just writing it in the new wrapper. 那行得通,但感觉确实很不雅致,因为它基本上使编写上述装饰器毫无意义,而不仅仅是将其编写在新包装器中。

Finally I found a neater trick to decorate the free function. 最终,我发现了一个巧妙的技巧来装饰自由功能。 By using %pythonprepend on the function I could insert something (anything, a comment, pass , empty string etc.) which as enough to suppress the extra code that was preventing #3 from working. 通过在函数上使用%pythonprepend ,我可以插入一些东西(任何东西,注释, pass ,空字符串等),这些东西足以抑制妨碍#3正常工作的多余代码。

The final problem I encountered was that to make it all work as a single macro and get the position of the %pythoncode directive right (also still permit %include ing of the header file which contained the declaration) I had to call the macro before %include . 我遇到的最后一个问题是,要使其全部作为单个宏工作并正确获得%pythoncode指令的位置(还允许%include包含声明的头文件),我必须在%include之前调用宏%include That necessitated adding an additional %ignore to ignore the function if/when it's seen a second time in an actual header file. 如果在实际的头文件中第二次看到该函数,则有必要添加一个额外的%ignore来忽略该函数。 However the other problem it introduced is that we now wrap the function before the struct, so inside the Python module the type of the struct we need the decorator to populate isn't yet known when we call the decorator. 但是,它引入的另一个问题是我们现在将函数包装在结构之前,因此在Python模块内部,调用装饰器时尚不知道需要装饰器填充的结构的类型。 That's easily enough fixed by passing a string to the decorator instead of a type and looking it up later, in the module globals() . 通过将字符串而不是类型传递给装饰器,然后在模块globals()查找它,很容易解决。

So with that said the complete, working interface that wraps this becomes: 因此,将其包装的完整,有效的接口变为:

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

This then was enough to work with the following Python code: 这样就足以使用以下Python代码:

import test

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

test.ComplexFun(flags=100, swizzle=True)

Things you'd probably want to do before using this for real: 在真正使用它之前,您可能想做的事情:

  1. With this decorator and kwargs as currently written it's pretty hard to get any kind of TypeError back. 使用目前这样编写的装饰器和kwargs,很难收回任何类型的TypeError。 Probably your C function has a way of indicating invalid combinations of inputs. 可能您的C函数有一种指示无效输入组合的方式。 Convert those into TypeError exceptions for Python users. 将这些转换为Python用户的TypeError异常。
  2. Adapt the macro to support mandatory positional arguments if needed. 如果需要,请修改宏以支持必需的位置参数。

Flexo's decoration is very impressive. 柔印的装饰令人印象深刻。 I came across this problem for myself and hesitate to propose my solution except that it has one saving grace: simplicity. 我自己遇到了这个问题,不愿提出我的解决方案,只是它有一个节省的余地:简单。 Also, my solution was for C++, but you might be able to modify it for C. 另外,我的解决方案是针对C ++的,但是您可能可以针对C进行修改。

I declare my OptArgs struct like this: 我这样声明我的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; }
}

with the intention of calling the constructor from C++ with MyClass(required_arg, OptArgs().b(2.71)) for example. 例如,目的是使用MyClass(required_arg, OptArgs().b(2.71))从C ++调用构造函数。

Now I use the following in the .i file to move the SWIG-generated constructor out of the way and unpack keyword arguments: 现在,我在.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)
    %}
};

It's not perfect: it relies on the extension happening after the __init__ generated by SWIG has been declared, and is python-specific, but seems to work OK and is very, very simple. 这不是完美的:它依赖于在声明由SWIG生成的__init__之后发生的扩展,并且该扩展是python特定的,但是似乎可以正常运行并且非常非常简单。

I hope that's helpful. 希望对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用带有** kwargs参数的函数的argparse - Using argparse with function that takes **kwargs argument 使用 *args、**kwargs 和可选/默认参数调用 Python 函数 - Calling a Python function with *args,**kwargs and optional / default arguments TypeError:function(self,item,** kwargs)正好占用2个参数(给定3个) - TypeError: function(self, item, **kwargs) takes exactly 2 arguments (3 given) 使用kwargs将参数传递给函数-Python - passing arguments to a function using kwargs - Python Python:带多个可选参数的Graph函数 - Python: Graph function that takes multiple optional arguments 在 python 中使用可选的 **kwargs - Using optional **kwargs in python 整齐地将位置参数作为args和可选参数传递为从argparse到函数的kwargs - Neatly pass positional arguments as args and optional arguments as kwargs from argparse to a function 如何避免在将 function 传递给另一个 function 时使用不同的 arguments 时过度使用 **kwargs? - How to avoid overuse of **kwargs in situation when passing function that takes differing arguments to another function? Dask 可视化颜色和可选参数 (graphviz kwargs) - Dask Visualise Color and Optional Arguments (graphviz kwargs) 使用** kwargs将传递给函数的参数映射到本地名称空间? - mapping arguments passed to function to local namespace using **kwargs?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM