在C ++中初始化Python C-API容器类型(List Dict Tuple Set)的适当语法

时间:2014-12-16 09:05:13

标签: python c++11 types python-c-api

我正在设计一个C ++ Python包装器。

我有一个Object类来包装泛型PyObject *,我提供构造函数和转换操作符,以便我可以执行以下操作:

// C++ type -> PyObject (of appropriate PyFoo_Type)
Object i{42}; // create a PyObject of type PyLong_Type
Object f{3.14}; // create a PyObject of type PyLong_Type

// PyFoo_Type -> C++ type
std::string s{f}; // convert PyObject to PyString_Type, then convert to std::string

我目前正在研究如何初始化容器:PyDict_Type PyList_Type PyTuple_Type PySet_Type(我认为这就是一切?)

看来我可以将其分解为两种情况:PyList_TypePyDict_Type。因为对于{PyList_Type, PyTuple_Type, PySet_Type}我可以初始化PyList_Type并随后转换它。

我的问题是:我应该提供什么C ++语法?

看到我的目标是开源我的项目以供公众消费,我需要注意提供一个可用的界面,而不是现有的设计模式。

以下所有内容都是我的思考过程,也是我自己尝试回答这个问题的过程。我会把它分开。此外,我很抱歉这可能是一个悬而未决的问题(不止一个解决方案)。


(适用THOUGHTS ...)

我想说我要初始化一个列表:

Object l{1,2,3,"four",5}; // notice each item may be of a different type

std::initializer_list<Object>将是一个选项,它会隐式地将每个参数强制转换为Object

但是现在想要Object{1}得到什么呢?可能是PyInt_Type。但是initializer_list构造函数会掩盖/隐藏它。

然后是字典:

Object d{K1Type k1:V1Type v1, K2Type k2:V2Type v2, ... }; // ILLEGAL C++ syntax!

同样,key2可能与key1的类型不同,这对于Python来说是件好事。

在C ++中,我不认为有任何方法可以使用:,但我可以通过{ {,}, {,}, {,} }std::map<Object,Object>来抓住std::pair

所以看起来List是最大的问题。

我唯一可以想到的是第一个参数指定类型:

Object{ PyList_Type, 1,2,3,"four",5 };
Object{ PyDict_Type, k1, v1, k2, v2, etc };

这对于输入字典会有一些优势,因为额外的括号会导致混乱,尽管我也应该允许:

Object{ std::map<Object,Object> };

所以我认为我的完整构造函数 - 容器看起来像这样:

// list, tuple, set
Object( PyTypeObject type_, std::initializer_list<Object> args ) {...} 

// dict { {,} , {,} , ... } style
Object( std::map<Object,Object> ) {...} 

// dict { , , , ...} style (less typing)
Object( PyTypeObject type_, std::map<Object,Object> ) {...} 

然后为了对称我应该提供:

// for everything
Object( PyTypeObject type_, Object& ob ) {...}

...允许用户执行Object f{PyFloat_Type, 3}之类的操作(3通常会转换为PyLong_Type)

但是等等! 3不会被隐式转换为Object,因为隐式转换仅适用于单参数构造函数。

Object i{PyFloat_Type, Object{3}}; // bit yucky

也许我应该创建一个MakeLong函数,所以我可以这样做:

Object f{ MakeFloat(3) };

但是等等,我不能在这里使用std::initializer_list构造函数,

Object f{ PyFloat_Type, {3} };

然后我可以编码:

// list, tuple, set
Object( PyTypeObject type_, std::initializer_list<Object> args ) 
{
    // first convert args to a PyList_Type PyObject*, say l
    // if type_ ISN'T {PyDict_Type PyList_Type PyTuple_Type PySet_Type}
    //      x = l[0] (and assert len(l)==1) else x = l
    // then get Python-Runtime to convert x into "type_"
}

这将处理所有事情!

首先,它会将{3}转换为包含一个PyLong_Type的列表。 然后它将拉出第一个(也是唯一的)元素。 然后它会将其转换为PyFloat_Type。

1 个答案:

答案 0 :(得分:0)

我尝试的第一个解决方案是使用std :: initializer_list,如下所示:

    Object( PyTypeObject& _type, std::initializer_list<Object> args )
    {
        if( &_type == &PyDict_Type )
        {
            Object dict{ PyDict_New() };
            for( auto p = args.begin(); p != args.end(); )
            {
                Object key{ *p }; p++; Exception::wrap( p != args.end(), "Dict init requires even # of args" );
                Object val{ *p }; p++;

                PyObject_SetItem( dict.p, key.p, val.p );
            }

            *this = dict;

            return;
        }

        // first unpack args into a PyList
        Object list{ PyList_New(0) };
        for( auto ob : args )
            PyList_Append( list.p, ob.p );

        if( &_type == &PyList_Type
         || &_type == &PyTuple_Type
         || &_type == &PySet_Type )
        {
            *this = list.convert_to(_type);

            return;
        }

        *this = list[0];
    }

    Object( const char c, std::initializer_list<Object> args )
    {
        if( c=='L' ) *this = Object{  PyList_Type, args };
        if( c=='T' ) *this = Object{ PyTuple_Type, args };
        if( c=='S' ) *this = Object{   PySet_Type, args };
        if( c=='D' ) *this = Object{  PyDict_Type, args };
    }

然而,这会使一些严重丑陋的语法,因为它无法将参数转换为Objects,似乎需要:

 Object{ 'L', { Object{42}, Object{3.14}, etc. } };

所以我决定使用一个可变参数:

    template<typename Foo, typename ... More>
    void unpack_to_list( Object& list, Foo&& foo, More&& ... more ) {
        PyList_Append( list.p, Object{foo}.p ); // I *think* PyList_Append expects a neutral pointer
        unpack_to_list( std::forward<More>(more) ... );
    }

    void unpack_to_list( Object& list )  { } // recursion terminator

    //template< Car car, Cdr ... cdr >
    template<typename ... Arg>
    Object( PyTypeObject& _type, Arg&& ... arg )
    {
        Object list{ PyList_New(0) };
        unpack_to_list( list, std::forward<Arg>(arg) ... );
        Py_ssize_t N = PyObject_Length( list.p );

        if( &_type == &PyDict_Type )
        {
            Exception::wrap( N % 2 == 0, "must supply an even number of arguments to dictionary" );

            Object dict{ PyDict_New() };

            int i=0;
            while( i < N )
                PyObject_SetItem( dict.p, list[i++].p, list[i++].p );

            *this = dict;
        }

        else if(
                & _type == & PyList_Type
             || & _type == & PyTuple_Type
             || & _type == & PySet_Type    )
        {
            *this = list.convert_to(_type);

            return;
        }

        else
            *this = list[0];
    }

    template<typename ... Arg>
    Object( const char c, Arg&& ... arg )
    {
        if( c=='L' ) *this = Object{  PyList_Type, std::forward<Arg>(arg) ... };
        if( c=='T' ) *this = Object{ PyTuple_Type, std::forward<Arg>(arg) ... };
        if( c=='S' ) *this = Object{   PySet_Type, std::forward<Arg>(arg) ... };
        if( c=='D' ) *this = Object{  PyDict_Type, std::forward<Arg>(arg) ... };
    }

这种技术允许使用整洁的轻量级语法:

Object myList{ 'L', 42, 3.14, "foo", etc. };
Object myDict{ 'D', "key1", val1, "key2", val2, etc. };