質問

I have an in-house library written in C++ that I'm currently working on extending into Python. I started this task with Boost.Python in mind, but I'm open to alternatives.

Currently I have a C++ function that needs to accept a Python class instance, and then use the methods of this object to perform certain tasks. The idea is for the Python user to never need to deal with C++. They're expected to create this Python object from a Python template/example class that I will provide, with preset method names which I can assume to be there in my C++ library.

The interface exposed to the Python user looks like:

class Foo(object):

    def __init__(self, args):
        """create an instance of this class with instance-specific attributes"""

    def Bar1(self, a, b, c):
        """do something with the given integers a, b and c"""
        pass

    def Bar2(self, a, b, c):
        """do something else with the given integers a, b and c"""
        pass

import mylib

cheese = mylib.Wine()
cheese.do_something(Foo)

In C++, the corresponding code looks like:

#include <boost/python.h>
#include <Python.h>

class Wine {

public:  

    Wine() {};

    ~Wine() {};

    static void do_something(boost::python::object *Foo) {

        int a = 1;
        int b = 2;
        int c = 3;

        Foo->attr("Bar1")(a, b, c);
        Foo->attr("Bar2")(a, b, c);
    };
};

BOOST_PYTHON_MODULE(mylib)
{
    using namespace boost::python;
    class_<Wine>("Wine")
        .def("do_something", &Wine::do_something);
};

I have successfully compiled this code and verified that the C++ class called Wine is really exposed to Python and I can access its member functions. If I write a member function called "greet()" that only returns "Hello, world!", it works perfectly.

I need to stress here the importance of passing an instance of Foo. I do not have the luxury to simply import the Foo module into C++ code and create an instance of Foo in C++. The object that I want to receive from the Python user has attributes I need to use that is specific to the instance, and not to the class itself.

Problem is that I can't figure out how to pass a Python instance into do_something, such that it will appear in C++ as a callable boost::python::object. The above code returns the following C++ signature mismatch error:

Boost.Python.ArgumentError: Python argument types in
    Wine.do_something(Wine, Foo)
did not match C++ signature:
    do_something(boost::python::api::object*)

Two days of perusing the internet for answers has yielded no progress. There seems to be a wealth of information on how to pass C++ classes into Python, but I was unable to find information on doing this in the opposite direction. Would really appreciate some guidance here.

Thanks!

役に立ちましたか?

解決

There are two errors in the initial code:

  • Boost.Python is attempting to pass two arguments (self and an instance of Foo) to the static Wine::do_something() C++ function that only accepts one argument. To resolve this, when exposing the Wine class, the Python Wine.do_something() member function needs to be set as static via the boost::python::class_::staticmethod() member function. When exposed as a static method, Boost.Python will no longer pass the self instance argument.
  • Unlike the Python/C API, where pointers are often used as handles to objects (PyObject*), Boost.Python provides a higher-level notation boost::python::object class that is often passed around by value or reference. Internally, this class interacts with a boost::python::handle that performs smart pointer management for PyObject.

Here is a complete Python extension based on the original code:

#include <boost/python.hpp>

class Wine
{
public:  

  static void do_something(boost::python::object object)
  {
    int a = 1;
    int b = 2;
    int c = 3;

    object.attr("Bar1")(a, b, c);
    object.attr("Bar2")(a, b, c);
  };
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<Wine>("Wine")
    .def("do_something", &Wine::do_something)
      .staticmethod("do_something")
    ;
};

Interactive usage:

>>> class Foo(object):
...     def Bar1(self, a, b, c):
...         print "Bar1", locals()
...     def Bar2(self, a, b, c):
...         print "Bar2", locals()
... 
>>> import example
>>> cheese = example.Wine()
>>> cheese.do_something(Foo())
Bar1 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}
Bar2 {'a': 1, 'c': 3, 'b': 2, 'self': <__main__.Foo object at 0xb6b0f2ac>}

他のヒント

To expose a method which accepts a Python object as an argument you should use boost::python::object not boost::python::object *

void do_something(boost::python::object Foo)

To expose a static method expose it like a regular function: def("do_something", Wine::do_something);

import mylib

# method
cheese = mylib.Wine()
foo = Foo()
cheese.do_something(foo)

#static method
foo = Foo()
mylib.do_something(foo)
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top