不透明容器

通常情况下,当调用接受相应C++容器的C++函数时,会传递Python容器,如listdict(参见container-type)。

这意味着每次调用时,整个Python容器都会被转换为C++容器,这在例如从点列表创建图表时可能效率低下。

为了解决这个问题,可以生成特殊的不透明容器,这些容器直接包装底层的C++容器(目前已经为list类型实现)。它们实现了序列协议,并且可以传递给函数,而不是Python列表。像添加或删除元素这样的操作可以直接使用C++容器函数对它们进行操作。

这是通过在container-typeopaque-containers属性中指定名称和实例化类型,或者使用opaque-container元素来实现现有容器类型的。

第二个用例是容器类型的公共字段。在正常情况下,它们在读取访问时会被转换为Python容器。通过字段修改(参见modify-field),可以获得一个不透明的容器,这样可以避免转换并允许直接修改元素。

返回引用的Getters也可以修改为返回不透明容器。 这是通过将返回类型修改为不透明容器的名称来实现的 (参见replace-type)。

下表列出了除了序列协议(通过索引和len()访问元素)之外,不透明序列容器支持的功能。同时支持STL和Qt命名约定(类似于Python的):

函数

描述

push_back(value), append(value)

value追加到序列中。

push_front(value), prepend(value)

value添加到序列的前面。

clear()

清除序列。

pop_back(), removeLast()

移除最后一个元素。

pop_front(), removeFirst()

移除第一个元素。

reserve(size)

对于支持它的容器 (std::vector, QList), 为至少 size 个元素分配内存,防止 重新分配。

capacity()

对于支持它的容器 (std::vector, QList), 返回可以在不重新分配的情况下 存储的元素数量。

data()

对于支持它的容器 (std::vector, QList), 返回一个查看内存的缓冲区。

constData()

对于支持它的容器 (std::vector, QList), 返回一个只读缓冲区查看 内存。

注意

std::span,作为一个非拥有容器,目前被std::vector替代用于参数传递。这意味着从函数中获得的封装std::span的不透明容器在传递给接受std::span的函数时,将通过序列转换转换为std::vector。封装std::vector的不透明容器可以直接传递而无需转换。这目前是实验性的,可能会有所变化。

以下是一个示例,展示了如何从std::vector创建一个名为IntVector的不透明容器,并在Python中使用它。

我们将考虑三个独立的使用案例。

案例 1 - 当Python列表作为不透明容器传递给C++函数 TestOpaqueContainer.getVectorSum(const std::vector&)

class TestOpaqueContainer
{
public:
    static int getVectorSum(const std::vector<int>& intVector)
    {
        return std::accumulate(intVector.begin(), intVector.end(), 0);
    }
};

案例 2 - 当我们有一个名为 TestOpaqueContainer 的 C++ 类,其中包含一个 std::vector 公共变量

class TestOpaqueContainer
{
public:
    std::vector<int> intVector;

};

案例 3 - 当我们有一个名为 TestOpaqueContainer 的 C++ 类,其中包含一个 std::vector 作为私有变量,并且该变量通过 getter 以引用形式返回。

class TestOpaqueContainer
{
public:
    std::vector<int>& getIntVector()
    {
        return this->intVector;
    }

private:
    std::vector<int> intVector;

};

注意

案例2和案例3通常被认为是C++中不良的类设计。然而,这些示例的目的更多是为了展示在Shiboken中使用不透明容器的不同可能性,而不是类设计。

在所有这三种情况下,我们都希望通过不透明容器在Python中使用intVector。首先要做的是在类型系统文件中创建相应的 />属性,使Shiboken知道IntVector

<container-type name="std::vector" type="vector" opaque-containers="int:IntVector">
    <include file-name="vector" location="global"/>
    <conversion-rule>
        <native-to-target>
            <insert-template name="shiboken_conversion_cppsequence_to_pylist"/>
        </native-to-target>
        <target-to-native>
            <add-conversion type="PySequence">
                <insert-template name="shiboken_conversion_pyiterable_to_cppsequentialcontainer"/>
            </add-conversion>
        </target-to-native>
    </conversion-rule>
</container-type>

对于其余的步骤,我们分别考虑这三种情况。

案例 1 - 当将 Python 列表传递给 C++ 函数时

作为下一步,我们为类 TestOpaqueContainer 创建一个类型系统条目。

<value-type name="TestOpaqueContainer" />

在这种情况下,类型系统条目很简单,函数 getVectorSum(const std::vector&) 接受 IntVector 作为参数。这是因为本质上 IntVectorstd::vector 是相同的。

现在,构建代码以创建我们将导入到Python中的*_wrapper.cpp*.so文件。

验证在Python中的使用

>>> vector = IntVector()
>>> vector.push_back(2)
>>> vector.push_back(3)
>>> len(vector)
2
>>> TestOpaqueContainer.getVectorSum(vector)
vector sum is 5

案例 2 - 当变量是公共的

我们为类TestOpaqueContainer创建一个类型系统条目。

<value-type name="TestOpaqueContainer">
    <modify-field name="intVector" opaque-container="yes"/>
</value-type>

/>中注意opaque-container="yes"。由于intVector的类型是std::vector,它选择了IntVector不透明容器。

构建代码以创建我们导入到Python中的*_wrapper.cpp*.so文件。

验证在Python中的使用

>>> test = TestOpaqueContainer()
>>> test
<Universe.TestOpaqueContainer object at 0x7fe17ef30c30>
>>> test.intVector.push_back(1)
>>> test.intVector.append(2)
>>> len(test.intVector)
2
>>> test.intVector[1]
2
>>> test.intVector.removeLast()
>>> len(test.intVector)
1

案例 3 - 当变量是私有的并通过getter以引用方式返回时

与之前的情况类似,我们为类 TestOpaqueContainer 创建了一个类型系统条目。

<value-type name="TestOpaqueContainer">
    <modify-function signature="getIntVector()">
        <modify-argument index="return">
            <replace-type modified-type="IntVector" />
        </modify-argument>
    </modify-function>
</value-type>

在这种情况下,我们在 />字段中指定了不透明容器IntVector的名称。

构建代码以创建我们导入到Python中的*_wrapper.cpp和*.so文件。

验证在Python中的使用

>>> test = TestOpaqueContainer()
>>> test
<Universe.TestOpaqueContainer object at 0x7f62b9094c30>
>>> vector = test.getIntVector()
>>> vector
<Universe.IntVector object at 0x7f62b91f7d00>
>>> vector.push_back(1)
>>> vector.push_back(2)
>>> len(vector)
2
>>> vector[1]
2
>>> vector.removeLast()
>>> len(vector)
1

在所有这三种情况下,如果我们查看模块对应的包装类,我们将看到以下行

static PyMethodDef IntVector_methods[] = {
    {"push_back", reinterpret_cast<PyCFunction>(
        ShibokenSequenceContainerPrivate<std::vector<int >>::push_back),METH_O, "push_back"},
    {"append", reinterpret_cast<PyCFunction>(
        ShibokenSequenceContainerPrivate<std::vector<int >>::push_back),METH_O, "append"},
    {"clear", reinterpret_cast<PyCFunction>(
        ShibokenSequenceContainerPrivate<std::vector<int >>::clear), METH_NOARGS, "clear"},
    {"pop_back", reinterpret_cast<PyCFunction>(
        ShibokenSequenceContainerPrivate<std::vector<int >>::pop_back), METH_NOARGS,
        "pop_back"},
    {"removeLast", reinterpret_cast<PyCFunction>(
        ShibokenSequenceContainerPrivate<std::vector<int >>::pop_back), METH_NOARGS,
        "removeLast"},
    {nullptr, nullptr, 0, nullptr} // Sentinel
};

这意味着,上述方法可以在Python中使用IntVector不透明容器。

注意

Plot example 演示了一个使用不透明容器 QPointList 的示例,该容器包装了一个 C++ QList。可以在 这里 找到 QPointList 的相应类型系统文件。