Boost::Python- 是否可以自动转换 dict 为 std::map?

16 投票
3 回答
16841 浏览
提问于 2025-04-16 18:16

我有一个C++类,这个类里面有一个成员函数,可以接收从少到多的参数。我们把这些参数叫做a到f。所有的参数都有默认值。在我正在做的Python项目中,我想把这个类暴露给Python使用。目前,这个成员函数的样子大概是这样的:

class myClass {
    public:
    // Constructors - set a-f to default values.

    void SetParameters(std::map<std::string, double> &);
    private:
    double a, b, c, d, e, f;
}

void myClass::SetParameters(std::map<std::string, double> const& params) {
    // Code to iterate over the map, and set any found key/value pairs to their
    // corresponding variable.  i.e.- "a" --> 2.0, would set myClass::a to 2.0
}

理想情况下,在Python中,我希望能通过一个字典来实现这个功能:

>>> A = myModule.myClass();
>>> A.SetParameters({'a': 2.2, 'd': 4.3, b: '9.3'})

这样一来,用户就可以以任意顺序输入值,并且可以输入任意数量的值来覆盖默认值。你们觉得在boost::python中怎么实现这个功能呢?我觉得可以通过把map的输入改成boost::python对象,然后使用提取函数来实现。不过,这样的话我就得改变我库的接口(我更希望保持std::map的接口,并为Python版本提供一些中间转换的技术)。你们有什么想法吗?

3 个回答

0

我刚开始接触boost::python,所以不能完全回答你的问题。不过我看到的第一个问题是,确保Python字典里的键都是字符串。因为在Python中,字典的键不仅可以是字符串,还可以是元组(我想可能还有其他类型)。

10

这篇博客文章对如何编写这些转换器有很清晰的描述。基本的模式是定义一个类,格式如下:

struct SomeType_from_PyObject
{
    SomeType_from_PyObject();
    static void* convertible(PyObject* obj_ptr);
    static void construct(PyObject* obj_ptr,
                          converter::rvalue_from_python_stage1_data* data);
};

这个类的构造函数负责把这个转换器添加到Boost.Python的注册表中:

SomeType_from_PyObject::SomeType_from_PyObject()
{
    converter::registry::push_back(&convertible,
                                   &construct,
                                   type_id<SomeType>());
}

函数 convertible 用来告诉Boost这个转换器是否可以转换指定的Python对象:

void* SomeType_from_PyObject::convertible(PyObject* obj_ptr)
{
    if (PyMapping_Check(obj_ptr)) {
        return obj_ptr;
    } else {
        return NULL;
    }
}

construct 函数则实际创建了一个转换类型的对象:

void SomeType_from_PyObject::construct(PyObject* obj_ptr,
                                       converter::rvalue_from_python_stage1_data* data)
{
    typedef converter::rvalue_from_python_storage<SomeType> storage_t;
    storage_t* the_storage = reinterpret_cast<storage_t*>(data);
    void* memory_chunk = the_storage->storage.bytes;
    object obj(handle<>(borrowed(obj_ptr)));
    SomeType* output = new (memory_chunk) SomeType();
    // Use the contents of obj to populate output, e.g. using extract<>
    data->convertible = memory_chunk;
}

然后在你的 BOOST_PYTHON_MODULE 里面,加入这一行:

SomeType_from_PyObject();
25

我觉得有几种方法比自己写转换器要简单得多。你可以使用boost::python的map_indexing_suite来帮你完成转换,或者你可以在Python中使用关键字参数。我个人更喜欢用关键字参数,因为这样更符合“Python风格”。

这是你的类(我为这个映射添加了一个类型定义):

typedef std::map<std::string, double> MyMap;

class myClass {
public:
    // Constructors - set a-f to default values.

    void SetParameters(MyMap &);
private:
    double a, b, c, d, e, f;
};

使用map_indexing_suite的例子:

#include <boost/python/suite/indexing/map_indexing_suite.hpp>

using boost::python;

BOOST_PYTHON_MODULE(mymodule)
{
    class_<std::map<std::string, double> >("MyMap")
        .def(map_indexing_suite<std::map<std::wstring, double> >() );

    class_<myClass>("myClass")
        .def("SetParameters", &myClass::SetParameters);
}

使用关键字参数的例子。这需要使用一个raw_function包装器:

using namespace boost::python;

object SetParameters(tuple args, dict kwargs)
{
    myClass& self = extract<myClass&>(args[0]);

    list keys = kwargs.keys();

    MyMap outMap;
    for(int i = 0; i < len(keys); ++i) {
        object curArg = kwargs[keys[i]];
        if(curArg) {
            outMap[extract<std::string>(keys[i])] = extract<double>(kwargs[keys[i]]);
        }               
    }
    self.SetParameters(outMap);

    return object();
}

BOOST_PYTHON_MODULE(mymodule)
{
    class_<myClass>("myClass")
        .def("SetParameters", raw_function(&SetParameters, 1));
}

这让你可以在Python中写这样的代码:

A.SetParameters(a = 2.2, d = 4.3, b = 9.3)

撰写回答