用户定义类型转换#

在创建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”类型。

_images/converter.png

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, &amp;pos, &amp;key, &amp;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>

注意

C++ 容器 std::liststd::vectorstd::pairstd::mapstd::spanstd::unordered_map 是内置的。要指定不透明容器,请使用 不透明容器 元素。仍可以指定 容器类型 以修改内置行为。对于这种情况,提供了一系列预定义的转换模板(参阅 预定义模板)。

变量 & 函数#

%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 方法,而现在 checkTypeadd-conversion 标签的属性。参考本页的第一个示例,您将能够将上述模板与新的转换规则定义方案联系起来。