Wrap a function that takes a struct of optional arguments using kwargs
<p>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. (Even though you should be able to rely on a compiler accepting <a href="http://stackoverflow.com/questions/9034787/function-parameters-max-number">at least 127 arguments to a function</a> nobody actually wants to write that many, especially as C has no overloading or default function argument support). As a hypothetical example we could consider the following struct/function pair (test.h) to illustrate the problem:</p>

<pre class="lang-c prettyprint-override"><code>#include &lt;stdbool.h&gt;

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

void ComplexFun(const ComplexArgs *arg) {}
</code></pre>

<p>When it comes to wrapping this using SWIG we can get something working quickly using:</p>

<pre class="lang-c prettyprint-override"><code>%module test

%{
#include "test.h"
%}

typedef bool _Bool;

%include "test.h"
</code></pre>

<p>That works and we can use it as follows:</p>

<pre class="lang-py prettyprint-override"><code>import test

args=test.ComplexArgs()
args.flags=100;
args.swizzle=True
test.ComplexFun(args)
</code></pre>

<p>But that isn't exactly Pythonic. A Python developer would be more accustomed to seeing kwargs used to support this kind of calling:</p>

<pre><code>import test

# Not legal in the interface currently:
test.ComplexFun(flags=100, swizzle=True)
</code></pre>

<p>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. </p>

<p>Normally in Python the way to modify function arguments and return values is to use a decorator. As a starting point I sketched out the following decorator, which solves the problem:</p>

<pre><code>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
</code></pre>

<p>It has some further neat properties when written like that:</p>

<ol>
<li>It also doesn't break the syntax for calling the function directly with the single struct argument</li>
<li>It can support functions with mandatory positional arguments <em>and</em> a struct full of optional arguments as the last argument. (Although it can't use kwargs syntax for mandatory non-struct arguments currently)</li>
</ol>

<p>The question then becomes one of simply applying that decorator to the right function inside the SWIG generated Python code. 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 <a href="http://swig.10945.n7.nabble.com/python-decorators-td12134.html" rel="nofollow">not the only one</a>) I initially tried:</p>

<ol>
<li><code>%feature("shadow")</code> - 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.</li>
<li><code>%feature("autodoc")</code> and <code>%feature("docstring")</code> - optimistically I'd hoped to be able to abuse them slightly, but no joy</li>
<li><code>%pythoncode</code> right before the SWIG sees the function declaration on the C side. Generates the right code, but unfortunately SWIG immediately hides the function we decorated by adding <code>ComplexFun = _test.ComplexFun</code>. Couldn't find a way around it for quite a while.</li>
<li>Use <code>%rename</code> to hide the real function we call and then write a wrapper around the real function which was also decorated. That worked, but felt really inelegant, because it basically made writing the above  decorator pointless instead of just writing it in the new wrapper.</li>
</ol>

<p>Finally I found a neater trick to decorate the free function. By using <code>%pythonprepend</code> on the function I could insert something (anything, a comment, <code>pass</code>, empty string etc.) which as enough to suppress the extra code that was preventing #3 from working.</p>

<p>The final problem I encountered was that to make it all work as a single macro and get the position of the <code>%pythoncode</code> directive right (also still permit <code>%include</code>ing of the header file which contained the declaration) I had to call the macro before <code>%include</code>. That necessitated adding an additional <code>%ignore</code> to ignore the function if/when it's seen a second time in an actual header file. 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. That's easily enough fixed by passing a string to the decorator instead of a type and looking it up later, in the <a href="http://stackoverflow.com/a/990450/168175">module <code>globals()</code></a>.</p>

<p>So with that said the complete, working interface that wraps this becomes:</p>

<pre><code>%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"
</code></pre>

<p>This then was enough to work with the following Python code:</p>

<pre><code>import test

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

test.ComplexFun(flags=100, swizzle=True)
</code></pre>

<p>Things you'd probably want to do before using this for real:</p>

<ol>
<li>With this decorator and kwargs as currently written it's pretty hard to get any kind of TypeError back. Probably your C function has a way of indicating invalid combinations of inputs. Convert those into TypeError exceptions for Python users. </li>
<li>Adapt the macro to support mandatory positional arguments if needed. </li>
</ol>

python c swig