【问题标题】:Boost::Python- possible to automatically convert from dict --> std::map?Boost::Python- 可以从 dict 自动转换 --> std::map?
【发布时间】:2011-09-01 06:16:07
【问题描述】:

我有一个 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 中,我想使用 dict 来完成此操作:

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

这样,用户可以按任何顺序输入值,并输入任意数量的值以被覆盖。关于如何在 boost::python 中实现这一点的任何想法?在我看来,我可以通过将地图输入更改为 boost::python 对象并使用提取函数来做到这一点。但是,这需要我更改库的接口(我更愿意保留 std::map 接口,并为 python 版本提供一些中间/自动转换技术)。想法?

【问题讨论】:

    标签: c++ python boost type-conversion boost-python


    【解决方案1】:

    我认为有几种方法比编写自己的转换器更容易完成。您可以使用 boost::python 的 map_indexing_suite 为您进行转换,也可以在 python 中使用关键字参数。我个人更喜欢关键字参数,因为这是更“Pythonic”的方式。

    这是你的课程(我为地图添加了 typedef):

    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)
    

    【讨论】:

    • @aleksey- 关键字参数方法看起来最适合我想做的事情。但是,我对您发布的代码有一些问题。看起来 raw_function 只喜欢指定的函数将元组和字典作为输入。类成员函数是否有单独的类似构造?
    • 哦,我明白了。我想我通过使用从我要包装的类继承的包装类来解决这个问题。
    • 另外,args 元组中的第一个参数应该是对你的类的引用。您可以省略 self 参数并从 args[0] 中提取 myClass&。看看这是否有效。
    • 效果很好!谢谢阿列克西!
    • 作为记录,map_indexing_suite 解决方案不起作用,因为不会应用隐式 "dict->std::map" from_python 转换器。因此,您将在运行时收到一个 ArgumentError 声明 "myClass.SetParameters(dict) did not match C++ signature : SetParameters(std::map [...]"
    【解决方案2】:

    This blog post 非常清楚地描述了如何编写这些转换器。基本模式是定义一个具有以下形式的类:

    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();
    

    【讨论】:

    • 非常感谢 Ray 的这篇精彩文章。我开始沿着这条路走下去(并且很可能会完成我正在做的工作,以了解更多关于 Python/C API 的信息。我认为 aleksay 的关键字参数方法将是我将如何去做,如果我能让它工作的话,但是这是非常有价值的东西。+1
    • 我同意,虽然转换器不是我解决 Mark 问题的方法,但我非常感谢关于 boost::python 转换器的好帖子。
    【解决方案3】:

    我刚刚被 boost::python 弄湿了,所以不能完全回答你的问题。但是我看到的第一个障碍是保证 py dict 的键都是字符串。 Python dicts 也可以在元组上键入(我假设更多类型)。

    【讨论】:

    • 任何可散列的东西都可以是键,一个字典可以有许多不同类型的键。
    猜你喜欢
    • 1970-01-01
    • 2013-08-23
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多