【问题标题】:swig: How to make a QList<T> iterable, like std::vector痛饮:如何使 QList<T> 可迭代,例如 std::vector
【发布时间】:2015-06-16 09:29:52
【问题描述】:

我正在使用 SWIG 为我的 qt 应用程序生成 Python 绑定。我有几个使用 QLists 的地方,我想从 SWIG 库中集成这些 QList,例如 std::vector(请参阅 http://www.swig.org/Doc1.3/Library.html#Library_nn15)。
这意味着:

  • QList 对象应可从 python 迭代(= 它们必须是可迭代的 Python 对象)
  • 应该可以将 python 列表传递给采用 qlist 的函数
  • ...以及 SWIG 库中列出的 std::vector 的所有其他功能

为此,我使用以下代码: https://github.com/osmandapp/OsmAnd-core/blob/master/swig/java/QList.i
后来在我使用 QLists 的课程中​​,我添加了如下代码:

%import "qlist.i"
%template(listfilter) QList<Interface_Filter*>;

class A {
    public:
    //.....
    QList<Interface_Filter*> get_filters();
};

到目前为止,这有效,但它并没有给我与 std::vector 的那种集成。 我无法找出 std_vector.i、std_container.i、...的哪些部分使对象可迭代。
我需要如何扩展 QList 接口文件以使我的 QList 可迭代?强>

【问题讨论】:

    标签: python c++ qt swig


    【解决方案1】:

    您所要求的——一个 qlist.i swig 文件在 python 中实现与 std_vector.i 对std::vector 的相同级别的集成——是一项不平凡的任务。

    我提供了一个非常基本的扩展 qlist.i 文件(以及 qlisttest.i 以向您展示如何使用它)并将尝试解释需要哪些步骤。

    qlist.i:

    %{
    #include <QList>
    %}
    
    %pythoncode %{
    class QListIterator:
        def __init__(self, qlist):
            self.index = 0
            self.qlist = qlist
        def __iter__(self):
            return self
        def next(self):
            if self.index >= self.qlist.size():
                raise StopIteration;
            ret = self.qlist.get(self.index)
            self.index += 1
            return ret
        __next__ = next
    %}
    
    template<class T> class QList {
    public:
    class iterator;
    typedef size_t size_type;
    typedef T value_type;
    typedef const value_type& const_reference;
    QList();
    size_type size() const;
    void reserve(size_type n);
    %rename(isEmpty) empty;
    bool empty() const;
    void clear();
    %rename(add) push_back;
    void push_back(const value_type& x);
    %extend {
        const_reference get(int i) throw (std::out_of_range) {
        int size = int(self->size());
        if (i>=0 && i<size)
            return (*self)[i];
        else
            throw std::out_of_range("QList index out of range");
        }
        void set(int i, const value_type& val) throw (std::out_of_range) {
        int size = int(self->size());
        if (i>=0 && i<size)
            (*self)[i] = val;
        else
            throw std::out_of_range("QList index out of range");
        }
        int __len__() {
        return self->size();
        }
        const_reference __getitem__(int i) throw (std::out_of_range) {
        int size = int(self->size());
        if (i>=0 && i<size)
            return (*self)[i];
        else
            throw std::out_of_range("QList index out of range");
        }
        %pythoncode %{
        def __iter__(self):
            return QListIterator(self)
        %}
    }
    };
    
    %define %qlist_conversions(Type...)
    %typemap(in) const QList< Type > & (bool free_qlist)
    {
    free_qlist = false;
    if ((SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0)) == -1) {
        if (!PyList_Check($input)) {
        PyErr_Format(PyExc_TypeError, "QList or python list required.");
        SWIG_fail;
        }
        Py_ssize_t len = PyList_Size($input);
        QList< Type > * qlist = new QList< Type >();
        free_qlist = true;
        qlist->reserve(len);
        for (Py_ssize_t index = 0; index < len; ++index) {
        PyObject *item = PyList_GetItem($input,index);
        Type* c_item;
        if ((SWIG_ConvertPtr(item, (void **) &c_item, $descriptor(Type *),0)) == -1) {
            delete qlist;
            free_qlist = false;
            PyErr_Format(PyExc_TypeError, "List element of wrong type encountered.");
            SWIG_fail;
        }
        qlist->append(*c_item);
        }
        $1 = qlist;
    }
    }
    %typemap(freearg) const QList< Type > &
    { if (free_qlist$argnum and $1) delete $1; }    
    %enddef
    

    qlisttest.i:

    %module qlist;
    %include "qlist.i"
    
    %inline %{
    class Foo {
    public:
        int foo;
    };
    %}
    
    %template(QList_Foo) QList<Foo>;
    %qlist_conversions(Foo);
    
    %inline %{  
    int sumList(const QList<Foo> & list) {
        int sum = 0;
        for (int i = 0; i < list.size(); ++i) {
            sum += list[i].foo;
        }
        return sum;
    }
    %}
    
    1. 包装 QList 以使其及其方法可从 python 访问
      这是通过使(部分)类定义可用于 swig 来实现的。这就是您当前的 qlist.i 所做的。
      注意:您可能需要为案例 QList&lt;T*&gt; 添加一个“模板专业化”,将 const_reference 类型定义为 const T*,因为您是使用QList 的指针。否则,QList&lt;T*&gt;::const_reference 将是 const T*&amp;,这显然会混淆 swig。 (见swig/Lib/std/std_vector.i

    2. python列表和QList之间的自动转换
      这通常通过使用 swig typemaps 来实现。例如,如果您希望函数 f(const QList&lt;int&gt;&amp; list) 能够接受 python 列表,则需要指定一个输入类型映射来执行从 python 列表到 QList&lt;int&gt; 的转换:

      %typemap(in) const QList<int> &
      {
          PyObject * py_list = $input;
          [check if py_list is really a python list of integers]
          QList<int>* qlist = new QList<int>();
          [copy the data from the py_list to the qlist]
          $1 = qlist;
      }
      %typemap(freearg) const QList<int> &
      { if ($1) delete $1; }
      

      这里的情况在几个方面更加困难:

      • 您希望能够传递一个 python 列表一个包装好的QList:要使其工作,您需要在类型映射中处理这两种情况。
      • 您想将包装类型T 的python 列表转换为QList&lt;T&gt;:
        这还涉及列表中每个元素从包装类型T 到普通T 的转换。这是通过 swig 函数SWIG_ConvertPtr 实现的。
      • 我不确定您是否可以使用模板参数指定类型映射。因此,我编写了一个 swig 宏 %qlist_conversions(Type),您可以使用它来将类型映射附加到特定 TypeQList&lt;Type&gt;

      对于其他转换方向(QList -> python 列表)你应该首先考虑你想要什么。考虑一个返回 QList&lt;int&gt; 的 C++ 函数。从 python 调用这个,应该返回一个包装好的 QList 对象,还是应该自动将 QList 转换为 python 列表?

    3. 将包装好的QList 作为python 序列访问,即,使len[] 在python 中工作
      为此,您需要使用%extend { ... } 扩展qlist.i 文件中的QList 类并实现__len____getitem__ 方法。

      如果切片也应该起作用,您需要为PySliceObjects 提供__getitem__(PySliceObject *slice)__ 成员方法和输入和“类型检查”类型映射。

      如果您希望能够使用 python 中的[] 修改包装的QList 中的值,则需要实现__setitem__

      有关您可以实现以实现更好集成的所有有用方法的列表,请参阅有关“内置类型”和“容器的抽象基类”的 python 文档。

      注意:如果您使用 swig -builtin 功能,那么您需要另外将上述功能注册到适当的“插槽”,例如使用

      %feature("python:slot", "sq_length", functype="lenfunc") __len__;
      
    4. 使包裹的QList可从python迭代
      为此,您需要扩展QList 类并实现一个返回python 迭代器对象的__iter__() 方法。

      python 迭代器对象是一个提供方法__iter__()__next__()(旧版python 为next())的对象,其中__next__() 返回下一个值并引发python 异常StopIteration 以表示结束.

      如前所述,您可以在 python 或 C++ 中实现迭代器对象。我展示了一个在 python 中执行此操作的示例。

    我希望这有助于您调整所需的功能。

    【讨论】:

    • 谢谢你,先生,提供(几乎完全)我所要求的。明天我会尝试代码,然后带着问题/cmets回来。我不知道这会这么复杂。最让我困惑的是,std_vector.i 可以与一条简单的行一起使用:%template(vectori) vector&lt;int&gt;;。他们是怎么做到的?!我们在这里遗漏了什么吗?
    • SWIG 包含一个函数库,这些函数以一种通用且更复杂的方式完成我在上面所做的工作。参见例如typemapsstd 用于语言无关代码,pycontainer.swgpyiterators.swg 用于相关 python 代码。
    • 但是,这很难使用。首先,他们建议实现文件:“除非你真的喝醉了,否则不要看它们”,这真的说明了一切......其次,它们是特定于 STL 的,并且 QList 不完全兼容 STL(例如,缺少reverse_iterator)
    • 啊,我只查看与语言无关的代码,从未遇到过pycontainer.swgpyiterators.swg 文件。我仍然可以将qlisttest.i 中的 2 行合并到一个定义中,然后我还有一个“单行,可以完成所有工作”的集成。
    【解决方案2】:

    您提供了对“如何使 python 对象可迭代”的问题的答案,但我询问了“如何扩展 QList 接口文件以使我的 QList 可迭代?”这更像是一个 SWIG,而不是一个与 python 相关的问题。

    我使用 Java、C# 和 Python 测试了来自 http://www.swig.org/Doc1.3/Library.html#Library_nn15 的示例。只有 Python 和 C# 提供迭代器。 Java 生成的接口没有实现Iterable 或类似的东西。据我所知,您的问题与目标语言有关。

    也许扩展MutableSequence 是您的一个选择。您必须实现的唯一方法是 __getitem____setitem____delitem____len__insert,方法是将它们委托给 QList 的相应方法。之后您生成的类是可迭代的。

    【讨论】:

    • 正如我在问题中所述,在 Swig 库的 std_vector.i 文件中,我无法在 python 中找到使 std_vector 可迭代的部分。您和 Sorin 的回答都非常通用,并且与 SWIG 无关。如赏金文本中所述,我希望看到一个完整的 qlist.i 接口文件,其行为类似于 std_vector.i 但在 QLists 上运行。
    【解决方案3】:

    docs 中所述,您需要以下内容:

    • QList 在 python __iter__() 中应该有一个方法,它返回一个迭代器对象(tp_iter,如果你在 C 中实现它)。
    • 迭代器对象应该实现__iter__()并返回自己
    • 迭代器对象应该实现next(),它返回下一项或在完成后引发StopIteration

    这可能在 python 中最容易实现,但您也可以在 C 中实现它。

    另一种选择是使用 python 生成器来避免实现迭代器类型。为此,您的 QList 需要实现 __iter__(),但您只需 yield 值而不是返回迭代器。

    提到的方法只需要对python可见。您不必使它们在 C/Java 中可用。

    另见SWIG interfacing C library to Python (Creating 'iterable' Python data type from C 'sequence' struct)

    【讨论】:

    • 您提供了“如何使 python 对象可迭代”的问题的答案,但我要求“我需要如何扩展 QList 接口文件以使我的 QList 可迭代?" 这更像是一个 SWIG,而不是一个与 python 相关的问题。
    • @Dreamcooled 我以为你知道。请参阅文档:swig.org/Doc1.3/Python.html#Python_nn43 - 31.6.3 使用 %extend 的类扩展。
    • 我确实知道%extend,但它还没有完成。如赏金文本中所述,我希望看到一个完整的 qlist.i 接口文件,其行为类似于 std_vector.i,但在 QLists 上运行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多