如何使用 SWIG 从 Python 使 C++ class 可迭代?

How to make a C++ class iterable from Python using SWIG?

我有一个 C++ class Collection 来管理 std::vector<Element>(class 的私有成员)。

在 C++ 中,我可以使用 begin()end() 迭代器(它们只是 vector 的迭代器的类型定义)来遍历向量,例如:

Collection col;
for (Collection::const_iterator itr = col.begin(); itr != col.end(); itr++)
{
  std::cout << itr->get() << std::endl;
}

现在我想做类似 Python 的事情,例如:

import example
el = example.Element()
el.set(5)
col = example.Collection()
col.add(el)
for e in col:
    print e.get()

但这会导致:

TypeError: 'Collection' object is not iterable

我无法以为 Python Collection [=59= 生成 __iter__(我认为这是它唯一需要的东西)的方式配置 SWIG ].我该怎么做?

这是我的代码:

example.h:

#include <vector>

class Element
{
public:
  Element();
  ~Element();

  int get() const;
  void set(const int var);

private:
  int variable_;
};

class Collection
{
public:
  Collection();
  ~Collection();

  void add(const Element& element);

  typedef std::vector<Element> tElements;

  // iterators
  typedef tElements::iterator iterator;
  typedef tElements::const_iterator const_iterator;
  iterator begin();
  const_iterator begin() const;
  iterator end();
  const_iterator end() const;

private:
  tElements          elements_;
};

example.cpp:

#include "example.h"

Element::Element() {}

Element::~Element() {}

int Element::get() const
{
  return variable_;
}

void Element::set(const int var)
{
  variable_ = var;
}

Collection::Collection() : elements_() {}

Collection::~Collection() {}

void Collection::add(const Element& element)
{
  elements_.push_back(element);
}

Collection::iterator Collection::begin()
{
  return elements_.begin();
}

Collection::const_iterator Collection::begin() const
{
  return elements_.begin();
}

Collection::iterator Collection::end()
{
  return elements_.end();
}

Collection::const_iterator Collection::end() const
{
  return elements_.end();
}

example.i:

%module example
%{
#include "example.h"
%}

// I've tried to add this, but that generates a whole
// other class, that is not what I want.
// %include "std_vector.i"
// %template(ElementVector) std::vector<Element>;

// I've also tried to %extend the class (which I think is what I want,
// but I cannot figure out with what to extend it with)

// Include the header file with above prototypes
%include "example.h"

编译为:

swig -python -c++ -o example_wrap.cpp example.i
g++ -fPIC -c example.cpp example_wrap.cpp -I/usr/include/python2.6
g++ -shared example.o example_wrap.o -o _example.so

受上一个例子的启发:。我想出了一个稍微不同的方法,它不使用变量来检查 StopIterator 异常状态。

此外,它仅使用 Collectionbegin() 结束 end() 迭代器,而无需公开(制作 publicstd::vector<Element> 本身。

example.i:

%module example
%{
#include "example.h"
%}

%inline %{
class StopIterator {};
class Iterator {
  public:
    Iterator(Collection::iterator _cur, Collection::iterator _end) : cur(_cur), end(_end) {}
    Iterator* __iter__()
    {
      return this;
    }
    Collection::iterator cur;
    Collection::iterator end;
  };
%}

%include "example.h"

%include "exception.i"
%exception Iterator::next {
  try
  {
    $action // calls %extend function next() below
  }
  catch (StopIterator)
  {
    PyErr_SetString(PyExc_StopIteration, "End of iterator");
    return NULL;
  }
}

%extend Iterator
{
  Element& next()
  {
    if ($self->cur != $self->end)
    {
      // dereference the iterator and return reference to the object,
      // after that it increments the iterator
      return *$self->cur++;
    }
    throw StopIterator();
  }
}

%extend Collection {
  Iterator __iter__()
  {
    // return a constructed Iterator object
    return Iterator($self->begin(), $self->end());
  }
};

我唯一想不通的是如何创建一个模板化的 Iterator 版本,这样我就可以传递任何 Iterator<Collection, Element> 而不必为每个 next() 重新定义模板实例化。欢迎提供解决方案 ;)

经过几个小时的编译器错误后,我能够使用 swig 宏概括@boaz001 解决方案。


iter.i


    %inline %{
    class SWIG_STOP_ITERATOR {};
    %}

    %define MAKE_CLASS_ITERABLE(
            CLASS,
            ITERATOR_CLASS,
            NEXT_RETURN_TYPE,
            HEADER)
    %inline %{
    class ITERATOR_CLASS {
            public:
            ITERATOR_CLASS(
            ##CLASS ::iterator _cur,
            ##CLASS ::iterator _end
            ) : cur(_cur), end(_end) {}
            ##ITERATOR_CLASS * __iter__()
            {
                return this;
            }
            ##CLASS ::iterator cur;
            ##CLASS ::iterator end;
    };
    %}
    %include HEADER
    %exception ITERATOR_CLASS::__next__ {
            try {
                $action;
            }
            catch (SWIG_STOP_ITERATOR)
            {
                PyErr_SetString(PyExc_StopIteration, "End of iterator");
                return NULL;
            }
    }

    %extend ITERATOR_CLASS {
            NEXT_RETURN_TYPE &__next__() {
                if ($self->cur != $self->end) {
                    return *$self->cur++;
                }
                throw SWIG_STOP_ITERATOR();
            }
    }

    %extend CLASS {
            ITERATOR_CLASS __iter__() {
                return ITERATOR_CLASS ($self->begin(), $self->end());
            }
    };
    %enddef

atom.h


#include <string>

    using namespace std;

    class Atom {
        public:
            Atom () = default;

            Atom (double x_, double y_ , double z_) :
                x(x_), y(y_), z(z_) {}

            Atom (double x_, double y_ , double z_, string name_) :
                    x(x_), y(y_), z(z_), name(move(name_)) {}

            double getX () const {
                return x;
            }
            void setX (double x_) {
                x = x_;
            }

            double getY () const {
                return y;
            }
            void setY (double y_) {
                y = y_;
            }

            double getZ () const {
                return z;
            }
            void setZ (double z_) {
                z = z_;
            }

            void setName(string name_) {
                name = move(name_);
            }

            string getName() {
                return name;
            }
        private:
            double x{};
            double y{};
            double z{};
            std::string name;
    };

node.h


#include atom.h
#include <vector>
#include <map>
    
using namespace std;

    class Node {
        public:
            Node () = default;

            ~Node () = default;

            void addAtom (Atom &atom_) {
                atoms.push_back(atom_);
            }

            Atom getAtom (int index) {
                return atoms.at(index);
            }

            typedef std::vector<Atom> tAtoms;
            typedef tAtoms::iterator iterator;
            typedef tAtoms::const_iterator const_iterator;

            //Define below 4 methods to make class compatible with iter.i

            iterator begin () {
                return atoms.begin();
            }

            const_iterator begin () const {
                return atoms.begin();
            }

            iterator end () {
                return atoms.end();
            }

            const_iterator end () const {
                return atoms.end();
            }
        private:
            tAtoms atoms;
     };

  • node.i

%module node
    %include "std_string.i"
    %include "std_vector.i"
    %include "iter.i"
    %{
    #include "atom.h"
    #include "node.h"
    %}
    MAKE_CLASS_ITERABLE(Node, NodeIterator, Atom, "node.h")
    %include "atom.h"

example.py


from node import *
from random import random
node = Node()
for _ in range(10):
    node.addAtom(Atom(random(), random(), random(), f'atom{_}'))

for atom in node:
    atom_coords = f'{atom.getX()}, {atom.getY()}, {atom.getZ()}'
    atom_name = atom.getName()
    print("Name: ", atom_name, '\n', 'Coordinates: ', atom_coords)