From: John M. <mcf...@gm...> - 2011-07-24 00:06:39
|
Thanks for your help Stefan. You are correct, I don't need to distinguish between the two calls for my own purposes. However, as my example shows, the typemap typechecking catches None as an invalid value, so that doesn't work as a way of indicating argument-not-present from Python. Honestly, it wouldn't hurt to be able to use it that way (I agree it is Pythonic), but the problem is that I need my typemap to work for both types of functions (required and optional from my example) -- if I treat None as valid in the typemap, it breaks "required*", but if I treat it is invalid, it breaks "optional*". Regardless, the %feature("compactdefaultargs") solution looks like it will work for me, so many thanks to you and Josh for pointing it out. Regards, John On 07/23/2011 06:14 PM, Stefan Zager wrote: > I guess what I'm wondering is: do you really need to distinguish between > these two calls: > > testmod.optional2(3) > testmod.optional2(3, None) > > ? > > I would argue that it's pythonic and intuitive to interpret 'None' in > the second call as equivalent to a literal 'NULL' in the C++ version of > the call: > > optional(3, NULL); > > If you *truly* need to distinguish these cases (and I am skeptical), > then I believe you can get the behavior you want by adding this to > testmod.i, before '%include "api.h"': > > %feature("compactdefaultargs") optional2; > > Stefan > > On Sat, Jul 23, 2011 at 8:04 AM, John McFarland <mcf...@gm... > <mailto:mcf...@gm...>> wrote: > > I think maybe my problem will best be explained with complete code. > I am attaching a very small set of code for a class "Object", an > API that uses that class (in fact the exact same API from my > previous message), and a swig interface file to wrap the API. My > objective is to write a typemap for the class Object so that I can > do my own conversion of its value, and that code should work with or > without a default NULL pointer. > > One thing I'm noticing is that I get different wrapper code > depending on some of the swig options, so for now let me use the > same options I have on my real project, which include: > -keyword (swig flag) > %feature("autodoc", "1"); > > My swig interface file uses a simple typemap to convert a Python > string into a C string and demonstrate some action using the Object > class. Obviously this isn't very useful as I've shown it, but it is > representative of the wrapping I need for my real project. > > So from Python, the testmod API expects a string where C++ uses > Object*. This may be either a required argument (required1 and > required2) or an optional argument (optional1 and optional2). If > the optional argument is not provided, then it should not trigger an > error, and we should not go into the typemap code. > > Now, here is the problem I was trying to explain originally. If I > build the module as is, this is the behavior I get: > > In [1]: import testmod > In [2]: testmod.required1('bob') > Creating object with string: bob > In required1: 0xbfd2024f > In [3]: testmod.required2(10,'bob') > Creating object with string: bob > In required2: 10 0xbfd2024f > In [4]: testmod.optional1() > ------------------------------__------------------------------__--------------- > ValueError Traceback (most recent > call last) > > /home/mcfarljm/testdir/swig___varargs/<ipython console> in <module>() > > /home/mcfarljm/testdir/swig___varargs/testmod.py in optional1(x) > 78 def optional1(x = None): > 79 """optional1(Object x = None)""" > ---> 80 return _testmod.optional1(x) > 81 > 82 def optional2(*args, **kwargs): > > ValueError: Expecting string > > In [5]: testmod.optional1('steve') > Creating object with string: steve > In optional1: 0xbfd2024f > In [6]: testmod.optional2(5) > In optional2: 5 0 > In [7]: testmod.optional2(5,'steve') > Creating object with string: steve > In optional2: 5 0xbfd2024f > > So the result is that the API works for every case except for when > optional1 is called without the optional argument being present. > Python thinks this is a type error, because the typemap code that > I wrote that checks PyString_Check($input) is being called on > Py_None. What I was trying to explain previously is that outside of > my typemap code, swig automatically inserts a NULL-pointer check; > here is a snippet from the wrapper code in wrap_optional1: > > if (obj0) { > { > if (! PyString_Check(obj0)) { > PyErr_SetString(PyExc___ValueError, "Expecting string"); > return NULL; > } > o1.set(PyString_AsString(obj0)__); > arg1 = &o1; > } > > The "if(obj0)" NULL pointer check only weeds out non-present > optional arguments (NULL pointers) when the Python functions are > wrapped with *args, since otherwise Python thinks the argument is > present (even though it has a default value of None), and > PyArg_ParseTupleAndKeywords converts obj0 from NULL into a Py_None > pointer. > > Sorry for all the detail, but I guess I couldn't explain myself very > well without it. In summary, I'm trying to write a typemap to do > argument conversion, and I want it to work for both optional and > non-optional arguments, but when Swig generates Python wrappers that > don't use *args, my typemap doesn't work for certain cases because > the non-present optional argument is not handled correctly. > > Any advice is definitely appreciated. > > Regards, > John > > > > On 07/23/2011 12:16 AM, Stefan Zager wrote: > > On Fri, Jul 22, 2011 at 3:35 PM, John McFarland > <mcf...@gm... <mailto:mcf...@gm...> > <mailto:mcf...@gm... <mailto:mcf...@gm...>>> wrote: > > Is there a reason that that is the intended behavior for the > case of > optional1 below but not optional2 (both have a default value > for x)? > > > Looking a bit more closely at the code, it appears that SWIG > tries to > use default args in the python code whenever doing so will allow > SWIG to > avoid generating overload resolution code for the method. In > this case, > SWIG doesn't recognize that optional2 meets that criterion. The > code it > generates is still correct, but it's perhaps not ideal. > > I don't know what you mean by the "default typemap" or that > it will > convert Py_None to NULL. The issue is that in the C wrapper > code, > the PyObject*, say obj0, may be either a NULL pointer or Py_None > after calling PyArg_ParseTuple, depending on whether swig > decided to > use *args or not. Swig automatically protects the typemap > with a > check like: > if (obj0) { > { > // Some typemap code > } > > > 'Some typemap code' is actually a call to SWIG_ConvertPtr, which > will > recognize Py_None and convert it to NULL for passing to the C++ > method. > Stefan > > |