swig:如何制作 QList<T>可迭代,如 std::vector [英] swig: How to make a QList<T> iterable, like std::vector

查看:30
本文介绍了swig:如何制作 QList<T>可迭代,如 std::vector的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 SWIG 为我的 qt 应用程序生成 Python 绑定.我有几个地方使用 QLists,我想整合这些 QList像来自 SWIG 库的 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;A类{民众://.....QListget_filters();};

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

解决方案

你要的是一个 qlist.i swig 文件,它在 python 中实现了与 std_vector 相同级别的 QList 集成.i 为 std::vector 做的事情——是一项重要的任务.

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

qlist.i:

%{#include %}%pythoncode%{类 QListIterator:def __init__(self, qlist):self.index = 0self.qlist = qlistdef __iter__(self):回归自我定义下一个(自己):如果 self.index >= self.qlist.size():引发停止迭代;ret = self.qlist.get(self.index)self.index += 1返回 ret__next__ = 下一个%}模板类 QList {民众:类迭代器;typedef size_t size_type;typedef T value_type;typedef const value_type&const_reference;QList();size_type size() const;无效储备(size_type n);%rename(isEmpty) 空;布尔空()常量;空清除();%rename(add) push_back;void push_back(const value_type& x);%延长 {const_reference get(int i) throw (std::out_of_range) {int size = int(self->size());如果 (i>=0 && isize());如果 (i>=0 && isize();}const_reference __getitem__(int i) throw (std::out_of_range) {int size = int(self->size());如果 (i>=0 && i&(bool free_qlist){free_qlist = 假;if ((SWIG_ConvertPtr($input, (void **) &$1, $1_descriptor, 0)) == -1) {如果 (!PyList_Check($input)) {PyErr_Format(PyExc_TypeError, "需要 QList 或 python 列表.");SWIG_fail;}Py_ssize_t len = PyList_Size($input);QList<类型 >* qlist = 新的 QList<类型 >();free_qlist = 真;qlist->reserve(len);for (Py_ssize_t index = 0; index append(*c_item);}$1 = qlist;}}%typemap(freearg) const QList<类型 >&{ if (free_qlist$argnum and $1) delete $1;}%enddef

qlisttest.i:

%module qlist;%include "qlist.i"%排队 %{类 Foo {民众:国际富;};%}%template(QList_Foo) QList;%qlist_conversions(Foo);%排队 %{int sumList(const QList & list) {整数总和 = 0;for (int i = 0; i < list.size(); ++i) {sum += list[i].foo;}返还金额;}%}

  1. 包装 QList 以使其及其方法可从 python 访问
    这是通过使(部分)类定义可用于 swig 来实现的.这就是您当前的 qlist.i 所做的.
    注意:您可能需要为 QList 的情况添加一个模板特化",将 const_reference 定义为 constT* 因为您使用的是 QList 指针.否则,QList::const_reference 将是 const T*&,这显然可能会混淆 swig.(参见 swig/Lib/std/std_vector.i)

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

    %typemap(in) const QList&{PyObject * py_list = $input;[检查 py_list 是否真的是一个 Python 整数列表]QList* qlist = new QList();【将py_list中的数据复制到qlist中】$1 = qlist;}%typemap(freearg) const QList&{ if ($1) 删除 $1;}

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

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

    对于另一个转换方向(QList -> python list),你应该首先考虑你想要什么.考虑一个返回 QList 的 C++ 函数.从 python 调用它,它应该返回一个包装的 QList 对象,还是它应该自动将 QList 转换为 python 列表?

  3. 将包装好的 QList 作为 python 序列访问,即让 len[] 从 python 工作
    为此,您需要使用 %extend { ... } 扩展 qlist.i 文件中的 QList 类并实现 __len__ 和 <代码>__getitem__ 方法.

    如果切片也可以工作,您需要提供一个 __getitem__(PySliceObject *slice)__ 成员方法以及 PySliceObject 的输入和类型检查"类型映射.

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

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

    注意:如果您使用 swig -builtin 功能,那么您需要使用例如

    额外将上述功能注册到适当的插槽"

    %feature("python:slot", "sq_length", functype="lenfunc") __len__;

  4. 使包装的 QList 可从 python 迭代
    为此,您需要扩展 QList 类并实现返回 Python 迭代器对象的 __iter__() 方法.

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

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

我希望这有助于作为您调整所需功能的基础.

I'm using SWIG to generate Python Bindings for my qt app. I have several places where I use QLists and I would like to integrate those QLists like std::vector from the SWIG Library (see http://www.swig.org/Doc1.3/Library.html#Library_nn15).
This means:

  • The QList objects should be iterable from python (= they must be an iterable python object)
  • It should be possible to pass a python list to a function which takes a qlist
  • ... and all the other features listed in the SWIG Library for std::vector

To achieve that I use the following Code: https://github.com/osmandapp/OsmAnd-core/blob/master/swig/java/QList.i
Later in my classes using QLists, I add code like:

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

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

This works so far, but it doesn't give me the kind of integration I get with std::vector. I'm having trouble finding out which parts of std_vector.i, std_container.i,... make an object iterable.
How do I need to extend the QList interface file to make my QList's iterable?

解决方案

What you are asking for -- a qlist.i swig file that achieves the same level of integration for QList in python as std_vector.i does for std::vector -- is a non-trivial task.

I provide a very basic extended qlist.i file (and qlisttest.i to show you how to use it) and will try to explain what steps are required.

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. Wrapping of QList to make it and its methods accessible from python
    This is achieved by making the (partial) class definition available to swig. That is what your current qlist.i does.
    Note: You might need to add a "template specialization" for the case QList<T*> that typedefs const_reference as const T* since you are using a QList of pointers. Otherwise, QList<T*>::const_reference will be const T*&, which apparently might confuse swig. (see swig/Lib/std/std_vector.i)

  2. Automatic conversion between python list and QList
    This is generally achieved by using swig typemaps. For instance, if you want a function f(const QList<int>& list) to be able to accept a python list, you need to specify an input typemap that performs the conversion from a python list to a QList<int>:

    %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; }
    

    Here, the situation is more difficult in several ways:

    • You want to be able to pass a python lists or a wrapped QList: For this to work, you need to handle both cases in the typemap.
    • You want to convert a python list of wrapped type T to a QList<T>:
      This also involves a conversion for every element of the list from the wrapped type T to the plain T. This is achieved by the swig function SWIG_ConvertPtr.
    • I am not sure if you can specify typemaps with template arguments. Therefore, I wrote a swig macro %qlist_conversions(Type) that you can use to attach the typemap to the QList<Type> for a specific Type.

    For the other conversion direction (QList -> python list) you should first consider what you want. Consider a C++ function that returns a QList<int>. Calling this from python, should this return a wrapped QList object, or should it automatically convert the QList to a python list?

  3. Accessing the wrapped QList as a python sequence, i.e., make len and [] work from python
    For this, you need to extend the QList class in the qlist.i file using %extend { ... } and implement __len__ and __getitem__ methods.

    If slicing should also work, you need to provide a __getitem__(PySliceObject *slice)__ member method and input and "typecheck" typemaps for PySliceObjects.

    If you want to be able to modify values in the wrapped QList using [] from python, you need to implement __setitem__.

    For a list of all the useful methods you can implement to achieve better integration, see the python documentation on "builtin types" and "abstract base classes for containers".

    Note: If you use the swig -builtin feature, then you need to additionally register the above functions to the appropriate "slots" using e.g.

    %feature("python:slot", "sq_length", functype="lenfunc") __len__;
    

  4. Making the wrapped QList iterable from python
    For this you need to extend the QList class and implement an __iter__() method that returns a python iterator object.

    A python iterator object is an object that provides the methods __iter__() and __next__() (next() for older python), where __next__() returns the next value and raises the python exception StopIteration to signal the end.

    As mentioned before, you can implement the iterator object in python or C++. I show an example of doing this in python.

I hope this helps as a basis for you to tweak the functionality that you require.

这篇关于swig:如何制作 QList&lt;T&gt;可迭代,如 std::vector的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆