[C++-sig] pyplusplus howto?

Matthias Baas baas at ira.uka.de
Mon Feb 13 16:34:35 CET 2006


Allen Bierbaum wrote:
> To help out I setup a temporary location for a pyplusplus faq.  Take a
> look at: https://realityforge.vrsource.org/view/PyOpenSG/PyPlusPlusFaq
> .  I haven't copied over all the questions that I have seen on the
> list yet but it is a start.  I would appreciate it if other people
> that are interested in using pyplusplus could help out by either
> migrating some of the questions/answers from the list or adding new
> questions and entries of their own.  If you want to ask questions or
> contribute to the faq you can register for the wiki at
> https://realityforge.vrsource.org/view/TWiki/TWikiRegistration .

Actually, instead of providing solutions using the current pyplusplus 
API I'd rather like to discuss a new/modified API that is already more 
straightforward to use.

As you've noticed from that other thread I'm currently evaluating 
pyplusplus by trying to wrap the SDK of a commercial software package. 
I'm not finished with that yet as I don't have a working version of the 
bindings yet (but I think I'm close), so I don't have any final 
conclusions yet, but so far it boils down to this: I like the concept 
and the functionality of pyplusplus, but I don't like the API (or 
rather: there is no API I could like or dislike, instead I have to mess 
right with the internals of pyplusplus). But then, it's still at version 
0.6.1, so it's ok if there's no absolutely stable and mature API. At 
least, so far the low level API has been flexible enough to deal with 
the problems I had and Roman has always been helpful with providing a 
solution (or fixing bugs). And I still believe that a higher level API 
could just be built on top of the existing package.

The question now is, what would an "ideal API" look like?

What I don't like about pyplusplus' way of filtering is that it 
encourages "messy" coding style ("spaghetti code"). You basically have 
one big filter function that must contain all rules and exceptions there 
are. That's fine when you have some global rules that apply to all 
classes, but when you need to do individual modifications to a 
particular class you end up with a lot of long winded tests. Then it's 
the same with the function that assigns call policies (i.e. the code 
dealing with a particular class is spread over your script whereas I 
would like to keep it together in one place). For such "specialized" 
changes that only affect one particular class or method, the API that 
Pyste offers is much more suited in my opinion (that's why I added my 
own functions).

One thing I didn't fully understand is the rationale behind the 
make_flatten() function. Why should this be used by a user of 
pyplusplus? The declarations are supposed to be seen as a tree, right? 
Then why don't we leave it what it is and provide an appropriate 
iterator instead? For example, in my script I defined the following 
generator:

# iterdecls
def iterdecls(rootdecl):
     """Iterate over one or more declaration trees.

     rootdecl can be either a single declaration, a list of declarations
     or None. A declaration must be an object derived from the declaration_t
     class.
     """
     if rootdecl==None:
         return

     if type(rootdecl) is not list:
         rootdecl = [rootdecl]

     for root in rootdecl:
         yield root
         childs = getattr(root, "declarations", None)
         for node in iterdecls(childs):
             yield node


Note that list(iterdecls(decl)) is equivalent to calling 
make_flatten(decl). But when you just want to iterate over the 
declarations you don't have to create a flattened list at all but 
instead write:

for decl in iterdecls(rootdecl):
   ...

With a slightly different interface the tree traversal can be guided so 
that parts of the tree are pruned and not traversed at all (something 
you cannot do with a flattened list).

The I defined a find_class() function that looks like this:

# find_class
def find_class(name):
     """Search for a class with a particular name.
     """
     global decls

     if name[:2]!="::":
         name = "::"+name

     res = declarations.find_first_declaration(decls, 
type=declarations.class_t, fullname=name)
     if res==None:
         raise RuntimeError, "Class '%s' not found"%name
     return res

The global decls variable was the root of the entire declaration tree 
(maybe it would be nice if pyplusplus would already store this). 
Basically, the function is equivalent to find_first_declaration(), but 
an important difference is that an exception is generated when the class 
was not found. You could argue that you would sooner or later also get 
an exception as find_first_declaration() returns None when it doesn't 
find the declaration, but this would hide the "true" source of the error 
and you wouldn't know which (missing) class was responsible for the error.
Then by defining an alias Class=find_class you already have something 
that looks familiar to Pyste users:

Foo = Class("Foo")

In this case, Foo is the declaration object corresponding to the class 
"Foo". Here, it would be nice if Foo would provide some convenience 
methods such as finding declarations of methods, etc. I did that with a 
function instead:

# find_method
def find_method(classdecl, name, retval=None, args=None):
     """Search for a method with a particular name.

     classdecl is the declaration object of the class where the method 
should
     be searched. name is the method name, retval the type of the return
     value and args a list of argument types. All types (retval and args)
     are given as strings.
     """
     res = []
     for decl in iterdecls(classdecl):
         if not isinstance(decl, declarations.member_calldef_t):
             continue
         if decl.name!=name:
             continue
         if retval!=None:
             if decl.return_type==None or 
decl.return_type.decl_string!=retval:
                 continue
         if args!=None:
             if len(args)!=len(decl.arguments):
                 continue
             cont_flag = False
             for arg,argument in zip(args, decl.arguments):
                 if arg!=argument.type.decl_string:
                     cont_flag = True
                     break
             if cont_flag:
                 continue

         res.append(decl)
     if res==[]:
         raise RuntimeError, "method '%s::%s' not 
found"%(declarations.full_name(classdecl), name)
     return res

This function returns a list of matching declarations. For example, you 
can invoke it like this:

methods = find_method(Foo, "bar", retval="bool", args=["int", "float"]) )

The next function I defined is the ignore function:

# ignore
def ignore(decls):
     """Mark one or more declarations as being ignored.
     """
     if type(decls) is not list:
         decls = [decls]

     for decl in decls:
         decl._ignore_flag = True

This just adds the attribute _ignore_flag to the declarations which 
indicates that this declaration should not appear in the bindings.
The filter could then just look like this:

def filter(decl):
     """Filter function.
     """
     # Check if the declaration was explicitly ignored
     if getattr(decl, "_ignore_flag", False):
         return False

     # default behavior
     return True

With this scheme (i.e. keeping the declaration tree and only mark the 
nodes that should be included/excluded) you can work with the binding in 
several ways. For example, you could either mark everything as being 
ignored and then selectively add the methods you want or just work the 
other way and include everything and then ignore some particular methods.
A similar scheme could be devised for assigning call policies, etc.

The bottom line: My suggestion would be to make the declaration tree 
more "feature rich" and make it the main data structure that the user 
will interact with. Each node should have a specialized API to 
manipulate the way that a binding for this particular object is created. 
The code creators that are later instantiated could get their parameters 
from the declaration tree.

- Matthias -




More information about the Cplusplus-sig mailing list