用户定义类型转换

在创建C++库的Python绑定过程中,大多数C++类将在Python领域中有代表它们的包装器。但可能还有其他类非常简单,并且/或者有Python类型作为直接对应物。(例如:一个表示复数的“Complex”类,在Python中有“complex”类型作为对应物。)这些类通常不会获得Python包装器,而是有从Python到C++以及从C++到Python的转换规则。

// 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

Qt for Python 期望 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>

容器转换

用于container-type的转换器与其他类型的转换器基本相同,只是它们使用了类型系统变量%INTYPE_#%OUTTYPE_#。Qt for Python将容器的转换代码与为容器定义(或自动生成)的转换结合起来。

<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::list, std::vector, std::pair, std::map, std::spanstd::unordered_map 是内置的。 要指定 Opaque Containers,请使用 opaque-container 元素。 container-type 仍然可以指定以修改内置行为。 对于这种情况,提供了许多预定义的转换模板(参见 Predefined Templates)。

变量与函数

%in

变量由C++输入变量替换。

%out

变量被C++输出变量替换。需要传达Python到C++转换的结果。

%INTYPE

用于Python到C++的转换。它被替换为正在定义转换的类型的名称。不要直接使用类型的名称。

%INTYPE_#

替换为容器中使用的第#种类型的名称。

%OUTTYPE

用于Python到C++的转换。它被替换为正在定义转换的类型的名称。不要直接使用类型的名称。

%OUTTYPE_#

替换为容器中使用的第#种类型的名称。

%CHECKTYPE[CPPTYPE]

被用于Python变量的Qt for Python类型检查函数替换。 C++类型由CPPTYPE表示。