Awasu » Embedding Python: Writing a C++ wrapper library (part 3)
Sunday 30th November 2014 8:04 AM []

Now, we'll convert this example, that defines a Python function, then calls it with some arguments.

Setting up the function

First, we need a method to compile Python code.

Object*
Object::compileString( const char* pCodeBuf )
{
    // compile the string 
    PyObject* pCodeObj = Py_CompileString( pCodeBuf , "" , Py_file_input ) ;
    if ( pCodeObj == NULL )
        Exception::translateException() ;
    return new Object( pCodeObj , true ) ; 
}

We call Py_CompileString() to compile the code, throw an exception if something went wrong, then return a new Object that wraps the PyObject Python returns us. When the caller has finished using it and deletes it, its reference count will be decremented and it will eventually get cleaned up[1]Assuming that no-one else has taken a reference to it..

Next we need to load the code into a module.

Object*
Object::execCodeModule( const char* pModuleName , Object& codeObj ) 
{
    // execute the code module 
    PyObject* pModuleObj = PyImport_ExecCodeModule( (char*)pModuleName , codeObj.pPyObject() ) ;
    if ( pModuleObj == NULL )
        Exception::translateException() ;
    return new Object( pModuleObj , true ) ; 
}

Pretty similar, except this time we return an Object that represents the module.

Finally, we want to locate our function in the module.

Object*
Object::getAttr( const char* pAttrName ) const
{ 
    // return the specified attribute
    PyObject* pAttrObj = PyObject_GetAttrString( pPyObject() , pAttrName ) ;
    if ( pAttrObj == NULL )
        Exception::translateException() ;
    return new Object( pAttrObj , true ) ; 
}

This is a general method that will search any object for a specific attribute, returning an Object representing that attribute (if it was found).

The example code to set up the function now looks like this.

// define our test function 
stringstream buf ;
buf << "def add( n1 , n2 ) :" << endl
    << "    return n1+n2" << endl ;
auto_ptr<Object> pCompiledFn( Object::compileString( buf.str().c_str() ) ) ; 
auto_ptr<Object> pModule( Object::execCodeModule( "test" , *pCompiledFn ) ) ;
auto_ptr<Object> pAddFn( pModule->getAttr( "add" ) ) ;

Preparing the function parameters

To set up the parameters to be passed into the function, we need to be able to create tuples and dictionaries.

Object* Object::newTuple( size_t n ) { PyObject* p=PyTuple_New(n) ; return new Object(p,true) ; }
Object* Object::newDict() { PyObject* p=PyDict_New() ; return new Object(p,true) ; }

We also need to be able to set items in a tuple.

void 
Object::setTupleItem( size_t pos , Object& obj )
{
    // set the tuple item
    int rc = PyTuple_SetItem( pPyObject() , pos , obj.pPyObject() ) ;
    if ( rc != 0 )
        Exception::translateException() ;
    // NOTE: The tuple takes ownership of the object, so it doesn't need to decref itself.
    obj.mDecRef = false ;
}

Note how we tell the Object being added that it no longer needs to decrement its reference count when it gets destroyed, because the tuple will take care of it.

The example code to set up the function parameters now looks like this[2]Note that this code uses a helper function that creates an Object from an int.:

// prepare the function parameters 
auto_ptr<Object> pPosArgs( Object::newTuple( 2 ) ) ;
pPosArgs->setTupleItem( 0 , Object(-2) ) ;
pPosArgs->setTupleItem( 1 , Object(15) ) ;

Calling the function

Now, all we have to do is call the function.

Object*
Object::callObj( Object& args , Object& kywdArgs ) 
{
    // call the object
    PyObject* pResultObj = PyObject_Call( pPyObject() , args.pPyObject() , kywdArgs.pPyObject() ) ;
    if ( pResultObj == NULL )
        Exception::translateException() ;
    return new Object( pResultObj , true ) ; 
}
// call the function
auto_ptr<Object> pKywdArgs( Object::newDict() ) ;
auto_ptr<Object> pResult( pAddFn->callObj( *pPosArgs , *pKywdArgs ) ) ;

callObj() returns an Object that represent the value returned by the function, so we need a method to repr a Python object, so we can print it out.

string
Object::reprVal() const 
{
    // return the "repr" value for the Python object
    // NOTE: We have to return a std::string since PyString_AsString() returns
    //  a char* for the "repr" object, which will be destroyed when we return.
    Object repr( PyObject_Repr(mpPyObject) , true ) ;
    return PyString_AsString( repr.pPyObject() ) ; 
}
cout << "The answer: " << pResult->reprVal() << endl ;
Download the source code here.


   [ + ]

1. Assuming that no-one else has taken a reference to it.
2. Note that this code uses a helper function that creates an Object from an int.
Have your say