用户定义类型转换#
在创建C++库的Python绑定过程中,大多数C++类都将有表示它们在Python世界的包装器。但可能还有其他非常简单且/或具有作为直接对应的Python类型的类。(例如:“Complex”类代表复数,在Python中对应“complex”类型。)这样的类,而不是获得Python包装器,通常有从Python到C++以及反向的转换规则。
// C++ class
struct Complex {
Complex(double real, double imag);
double real() const;
double imag() const;
};
// Converting from C++ to Python using the CPython API:
PyObject* pyCpxObj = PyComplex_FromDoubles(complex.real(), complex.imag());
// Converting from Python to C++:
double real = PyComplex_RealAsDouble(pyCpxObj);
double imag = PyComplex_ImagAsDouble(pyCpxObj);
Complex cpx(real, imag);
为了在正确位置插入用户定义的转换代码,必须使用 conversion-rule 标签。
<primitive-type name="Complex" target-lang-api-name="PyComplex">
<include file-name="complex.h" location="global"/>
<conversion-rule>
<native-to-target>
return PyComplex_FromDoubles(%in.real(), %in.imag());
</native-to-target>
<target-to-native>
<!-- The 'check' attribute can be derived from the 'type' attribute,
it is defined here to test the CHECKTYPE type system variable. -->
<add-conversion type="PyComplex" check="%CHECKTYPE[Complex](%in)">
double real = PyComplex_RealAsDouble(%in);
double imag = PyComplex_ImagAsDouble(%in);
%out = %OUTTYPE(real, imag);
</add-conversion>
</target-to-native>
</conversion-rule>
</primitive-type>
详细信息将在后面提供,但基本原理是标记 native-to-target,它只执行一次从C++到Python的转换,以及 native-to-native,它可以定义多个Python类型转换为C++的“Complex”类型。
Python for Qt期望native-to-target的代码直接返回转换的Python结果,并且必须在target-to-native中添加的转换将Python到C++转换结果分配给%out变量。
在上一个示例的基础上扩展,如果绑定开发者想使Python 2元组数字能够被带有“Complex”参数的包装C++函数接受,则需要添加一个add-conversion标签和自定义检查。以下是如何操作的
<!-- Code injection at module level. -->
<inject-code class="native" position="beginning">
static bool Check2TupleOfNumbers(PyObject* pyIn) {
if (!PySequence_Check(pyIn) || !(PySequence_Size(pyIn) == 2))
return false;
Shiboken::AutoDecRef pyReal(PySequence_GetItem(pyIn, 0));
if (!PyNumber_Check(pyReal))
return false;
Shiboken::AutoDecRef pyImag(PySequence_GetItem(pyIn, 1));
if (!PyNumber_Check(pyImag))
return false;
return true;
}
</inject-code>
<primitive-type name="Complex" target-lang-api-name="PyComplex">
<include file-name="complex.h" location="global"/>
<conversion-rule>
<native-to-target>
return PyComplex_FromDoubles(%in.real(), %in.imag());
</native-to-target>
<target-to-native>
<add-conversion type="PyComplex">
double real = PyComplex_RealAsDouble(%in);
double imag = PyComplex_ImagAsDouble(%in);
%out = %OUTTYPE(real, imag);
</add-conversion>
<add-conversion type="PySequence" check="Check2TupleOfNumbers(%in)">
Shiboken::AutoDecRef pyReal(PySequence_GetItem(%in, 0));
Shiboken::AutoDecRef pyImag(PySequence_GetItem(%in, 1));
double real = %CONVERTTOCPP[double](pyReal);
double imag = %CONVERTTOCPP[double](pyImag);
%out = %OUTTYPE(real, imag);
</add-conversion>
</target-to-native>
</conversion-rule>
</primitive-type>
容器转换#
容器转换器基本上与其他类型相同,只是它们使用%INTYPE_#和%OUTTYPE_#这样的类型系统变量。Python for Qt将容器的转换代码与容器定义的(或自动生成的)转换代码相结合。
<container-type name="std::map" type="map">
<include file-name="map" location="global"/>
<conversion-rule>
<native-to-target>
PyObject* %out = PyDict_New();
%INTYPE::const_iterator it = %in.begin();
for (; it != %in.end(); ++it) {
%INTYPE_0 key = it->first;
%INTYPE_1 value = it->second;
PyDict_SetItem(%out,
%CONVERTTOPYTHON[%INTYPE_0](key),
%CONVERTTOPYTHON[%INTYPE_1](value));
}
return %out;
</native-to-target>
<target-to-native>
<add-conversion type="PyDict">
PyObject* key;
PyObject* value;
Py_ssize_t pos = 0;
while (PyDict_Next(%in, &pos, &key, &value)) {
%OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key);
%OUTTYPE_1 cppValue = %CONVERTTOCPP[%OUTTYPE_1](value);
%out.insert(%OUTTYPE::value_type(cppKey, cppValue));
}
</add-conversion>
</target-to-native>
</conversion-rule>
</container-type>
变量 & 函数#
- %in
变量被替换为 C++ 输入变量。
- %out
变量被替换为 C++ 输出变量。需要用于传递 Python 到 C++ 转换的结果。
- %INTYPE
在 Python 到 C++ 转换中使用。它被替换为正在定义的转换类型的名称。不要直接使用类型的名称。
- %INTYPE_#
被替换为在容器中使用的第 # 个类型的名称。
- %OUTTYPE
在 Python 到 C++ 转换中使用。它被替换为正在定义的转换类型的名称。不要直接使用类型的名称。
- %OUTTYPE_#
被替换为在容器中使用的第 # 个类型的名称。
- %CHECKTYPE[CPPTYPE]
被替换为用于 Python 变量的 Qt for Python 类型检查函数。C++ 类型由
CPPTYPE
指示。
转换旧式转换器#
如果您使用 Qt for Python 进行绑定,并且使用了 Shiboken::Converter
模板定义了一些类型转换,那么您必须更新您的转换器到新方案。
之前您的转换规则声明在一行中,如下
<primitive-type name="Complex" target-lang-api-name="PyComplex">
<include file-name="complex.h" location="global"/>
<conversion-rule file="complex_conversions.h"/>
</primitive-type>
在单独的 C++ 文件中实现,如下
namespace Shiboken {
template<> struct Converter<Complex>
{
static inline bool checkType(PyObject* pyObj) {
return PyComplex_Check(pyObj);
}
static inline bool isConvertible(PyObject* pyObj) {
return PyComplex_Check(pyObj);
}
static inline PyObject* toPython(void* cppobj) {
return toPython(*reinterpret_cast<Complex*>(cppobj));
}
static inline PyObject* toPython(const Complex& cpx) {
return PyComplex_FromDoubles(cpx.real(), cpx.imag());
}
static inline Complex toCpp(PyObject* pyobj) {
double real = PyComplex_RealAsDouble(pyobj);
double imag = PyComplex_ImagAsDouble(pyobj);
return Complex(real, imag);
}
};
}
在这种情况下,将在新转换规则中将使用到的实现部分是最后两个方法 static inline PyObject* toPython(const Complex& cpx)
和 static inline Complex toCpp(PyObject* pyobj)
。已经删除了 isConvertible
方法,而现在 checkType
是 add-conversion 标签的属性。参考本页的第一个示例,您将能够将上述模板与新的转换规则定义方案联系起来。