问题
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!
回答1:
There are two errors in the initial code:
- Boost.Python is attempting to pass two arguments (
self
and an instance ofFoo
) to the staticWine::do_something()
C++ function that only accepts one argument. To resolve this, when exposing theWine
class, the PythonWine.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 theself
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 forPyObject
.
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>}
回答2:
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)
来源:https://stackoverflow.com/questions/23440197/passing-a-python-class-instance-into-a-c-function