如何使用 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 异常状态。
此外,它仅使用 Collection
的 begin()
结束 end()
迭代器,而无需公开(制作 public
)std::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)
我有一个 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 异常状态。
此外,它仅使用 Collection
的 begin()
结束 end()
迭代器,而无需公开(制作 public
)std::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)