我正在设计一个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_Type
和PyDict_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。
答案 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. };