mirror of
https://github.com/boostorg/python.git
synced 2026-01-20 16:52:15 +00:00
Compare commits
2 Commits
svn-branch
...
svn-branch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6b99ede25 | ||
|
|
7e672720f1 |
@@ -657,7 +657,7 @@ for i in S:
|
||||
for (iterator i = S.begin(), end = S.end(); i != end; ++i)
|
||||
</pre></blockquote>
|
||||
|
||||
<p>One could try to wrap C++ iterators in order to carry the C++ idiom into
|
||||
<p>One could try to wrap C++ iterators directly in order to carry the C++ idiom into
|
||||
Python. However, this does not work very well because
|
||||
|
||||
<ol>
|
||||
@@ -671,10 +671,78 @@ for (iterator i = S.begin(), end = S.end(); i != end; ++i)
|
||||
</ol>
|
||||
|
||||
<p>
|
||||
It is a better idea to support the standard <a
|
||||
href="http://www.pythonlabs.com/pub/www.python.org/doc/current/ref/sequence-types.html">Python
|
||||
sequence and mapping protocols</a> for your wrapped containers. These
|
||||
operators have to be wrapped manually because there are no corresponding
|
||||
Therefore, BPL provides a special helper class "cursor" that acts as
|
||||
an adapter for STL iterators and enables them to be used within
|
||||
normal Python loops. Suppose, for example, that we want to wrap a
|
||||
<code>std::list<BigNum></code>. First, we have to wrap this class itself,
|
||||
as usual:
|
||||
|
||||
<blockquote><pre>
|
||||
// wrap an STL conforming container
|
||||
boost::python::class_builder<std::list<BigNum> > bignum_list_class(my_module, "BigNumList");
|
||||
|
||||
bignum_list_class.def(boost::python::constructor<>());
|
||||
|
||||
bignum_list_class.def((void (std::list<BigNum>::*)(BigNum const &))
|
||||
&std::list<BigNum>::push_back, "push_back");
|
||||
</pre></blockquote>
|
||||
|
||||
Since <code>std::list</code> conforms to the requirements
|
||||
of an STL container, we may create a cursor for it:
|
||||
|
||||
<blockquote><pre>
|
||||
// define cursor for an STL conforming container
|
||||
my_module.def_cursor_for(bignum_list_class);
|
||||
</pre></blockquote>
|
||||
|
||||
This enables the following use of <code>BigNumList</code> within Python:
|
||||
|
||||
<blockquote><pre>
|
||||
>>> l = BigNumList()
|
||||
>>>
|
||||
>>> l.push_back(BigNum(1)) # fill the list
|
||||
>>> l.push_back(BigNum(2))
|
||||
>>> l.push_back(BigNum(3))
|
||||
>>>
|
||||
>>> for i in l.cursor(): # use list's cursor in a loop
|
||||
... print i
|
||||
...
|
||||
1
|
||||
2
|
||||
3
|
||||
</pre></blockquote>
|
||||
|
||||
The cursor defines random access functions ("__getitem__" and
|
||||
"__setitem__") for any iterator, but these functions will only be
|
||||
as efficient as the underlying iterator allows. Indices are in the
|
||||
range <code>[0, cursor.len()-1]</code>. You can always
|
||||
access items in any order:
|
||||
|
||||
<blockquote><pre>
|
||||
>>> cursor = l.cursor() # get cursor
|
||||
>>> cursor.len() # length of the sequence
|
||||
3
|
||||
>>> print cursor[2] # read element at index
|
||||
3
|
||||
>>> print cursor[0]
|
||||
1
|
||||
>>> cursor[1] = BigNum(42) # write element at index
|
||||
>>> print cursor[1]
|
||||
42
|
||||
</pre></blockquote>
|
||||
|
||||
but this may be slow (linear time per access) on a large list
|
||||
which provides only a forward or bi-directional iterator. Note that this
|
||||
is not a problem for the loop above because it always accesses items
|
||||
in forward order.
|
||||
|
||||
<p>
|
||||
An alternative way of wrapping your containers is to support
|
||||
Python's standard <a
|
||||
href="http://www.pythonlabs.com/pub/www.python.org/doc/current/ref/sequence-types.html">
|
||||
sequence and mapping protocols</a>. The special functions
|
||||
required by these protocols have to be wrapped manually because there
|
||||
are no corresponding
|
||||
C++ operators that could be used for automatic wrapping. The Python
|
||||
documentation lists the relevant <a href=
|
||||
"http://www.pythonlabs.com/pub/www.python.org/doc/current/ref/sequence-types.html">
|
||||
|
||||
@@ -144,7 +144,27 @@ class class_builder
|
||||
reference<detail::extension_class<T, U> > m_class;
|
||||
};
|
||||
|
||||
// The bug mentioned at the top of this file is that on certain compilers static
|
||||
namespace detail
|
||||
{
|
||||
|
||||
// helper function that does the actual work of creating a cursor for a n
|
||||
// STL conforming container. called by module_builder::def_cursor_for()
|
||||
template <class T, class U>
|
||||
void wrap_cursor_class(module_builder & module,
|
||||
class_builder<T, U> & wrapped_container)
|
||||
{
|
||||
std::string cursor_name(wrapped_container.get_extension_class()->tp_name);
|
||||
cursor_name += "_cursor";
|
||||
class_builder<cursor<T> > cursor_class(module, cursor_name.c_str());
|
||||
|
||||
cursor_class.def(&cursor<T>::get_item, "__getitem__");
|
||||
cursor_class.def(&cursor<T>::set_item, "__setitem__");
|
||||
cursor_class.def(&cursor<T>::len, "__len__");
|
||||
wrapped_container.def(&extension_class_cursor_factory<T>::get, "cursor");
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// global functions declared within the body of a class template will only be
|
||||
// generated when the class template is constructed, and when (for some reason)
|
||||
// the construction does not occur via a new-expression. Otherwise, we could
|
||||
|
||||
@@ -319,6 +319,98 @@ class read_only_setattr_function : public function
|
||||
string m_name;
|
||||
};
|
||||
|
||||
|
||||
/* helper class to wrap STL conforming iterators.
|
||||
|
||||
Given a wrapped container ("FooList", say), this template is used to create
|
||||
an auxiliary class "FooList_cursor" that wraps the container's iterator.
|
||||
The cursor can be used in Python loops likes this:
|
||||
|
||||
>>> for i in foo_list.cursor():
|
||||
... print i.get_data()
|
||||
|
||||
The auxiliary cursor class can be created for any STL conforming
|
||||
container. It implements random access functions (get_item() and
|
||||
set_item()) for any iterator, but these will only be as efficient as the
|
||||
underlying iterator allows. However, this is not a problem because
|
||||
the above Python loop accesses the items in forward order anyway.
|
||||
*/
|
||||
template <class Container>
|
||||
struct cursor
|
||||
{
|
||||
typedef typename Container::iterator iterator;
|
||||
typedef typename Container::value_type value_type;
|
||||
|
||||
cursor(Container & c, ref python_object)
|
||||
: m_python_object(python_object),
|
||||
m_begin(c.begin()),
|
||||
m_iter(c.begin()),
|
||||
m_size(c.size()),
|
||||
m_index(0)
|
||||
{}
|
||||
|
||||
void advance(int index, std::forward_iterator_tag)
|
||||
{
|
||||
if(index < 0 || index >= m_size)
|
||||
{
|
||||
PyErr_SetObject(PyExc_KeyError, BOOST_PYTHON_CONVERSION::to_python(index));
|
||||
throw python::error_already_set();
|
||||
}
|
||||
|
||||
int delta = index - m_index;
|
||||
if(delta < 0)
|
||||
{
|
||||
m_iter = m_begin;
|
||||
delta = index;
|
||||
}
|
||||
std::advance(m_iter, delta);
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
void advance(int index, std::bidirectional_iterator_tag)
|
||||
{
|
||||
if(index < 0 || index >= m_size)
|
||||
{
|
||||
PyErr_SetObject(PyExc_KeyError, BOOST_PYTHON_CONVERSION::to_python(index));
|
||||
throw python::error_already_set();
|
||||
}
|
||||
int delta = index - m_index;
|
||||
std::advance(m_iter, delta);
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
value_type const & get_item(int index)
|
||||
{
|
||||
advance(index, std::iterator_category(m_iter));
|
||||
return *m_iter;
|
||||
}
|
||||
|
||||
void set_item(int index, value_type const & v)
|
||||
{
|
||||
advance(index, std::iterator_category(m_iter));
|
||||
*m_iter = v;
|
||||
}
|
||||
|
||||
int len() const
|
||||
{ return m_size; }
|
||||
|
||||
ref m_python_object;
|
||||
iterator m_begin, m_iter;
|
||||
int m_index, m_size;
|
||||
};
|
||||
|
||||
/* create a cursor for an STL conforming container */
|
||||
template <class T>
|
||||
struct extension_class_cursor_factory
|
||||
{
|
||||
static cursor<T> get(ref container)
|
||||
{
|
||||
return cursor<T>(
|
||||
BOOST_PYTHON_CONVERSION::from_python(container.get(), type<T&>()),
|
||||
container);
|
||||
}
|
||||
};
|
||||
|
||||
template <class From, class To>
|
||||
struct define_conversion
|
||||
{
|
||||
|
||||
@@ -16,6 +16,17 @@
|
||||
|
||||
namespace boost { namespace python {
|
||||
|
||||
class module_builder;
|
||||
|
||||
template <class T, class U> class class_builder;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T, class U>
|
||||
void wrap_cursor_class(module_builder & module,
|
||||
class_builder<T, U> & wrapped_container);
|
||||
} // namspace detail
|
||||
|
||||
class module_builder
|
||||
{
|
||||
public:
|
||||
@@ -39,6 +50,15 @@ class module_builder
|
||||
{
|
||||
add(detail::new_wrapped_function(fn), name);
|
||||
}
|
||||
|
||||
// wrapped_container must wrap an STL conforming container;
|
||||
// this function creates a cursor that wraps this container's iterator
|
||||
// and adds the factory function "cursor()" to the wrapped container
|
||||
template <class T, class U>
|
||||
void def_cursor_for(class_builder<T, U> & wrapped_container)
|
||||
{
|
||||
detail::wrap_cursor_class(*this, wrapped_container);
|
||||
}
|
||||
|
||||
// Return true iff a module is currently being built.
|
||||
static bool initializing();
|
||||
|
||||
@@ -324,6 +324,98 @@ class read_only_setattr_function : public function
|
||||
string m_name;
|
||||
};
|
||||
|
||||
|
||||
/* helper class to wrap STL conforming iterators.
|
||||
|
||||
Given a wrapped container ("FooList", say), this template is used to create
|
||||
an auxiliary class "FooList_cursor" that wraps the container's iterator.
|
||||
The cursor can be used in Python loops likes this:
|
||||
|
||||
>>> for i in foo_list.cursor():
|
||||
... print i.get_data()
|
||||
|
||||
The auxiliary cursor class can be created for any STL conforming
|
||||
container. It implements random access functions (get_item() and
|
||||
set_item()) for any iterator, but these will only be as efficient as the
|
||||
underlying iterator allows. However, this is not a problem because
|
||||
the above Python loop accesses the items in forward order anyway.
|
||||
*/
|
||||
template <class Container>
|
||||
struct cursor
|
||||
{
|
||||
typedef typename Container::iterator iterator;
|
||||
typedef typename Container::value_type value_type;
|
||||
|
||||
cursor(Container & c, ref python_object)
|
||||
: m_python_object(python_object),
|
||||
m_begin(c.begin()),
|
||||
m_iter(c.begin()),
|
||||
m_size(c.size()),
|
||||
m_index(0)
|
||||
{}
|
||||
|
||||
void advance(int index, std::forward_iterator_tag)
|
||||
{
|
||||
if(index < 0 || index >= m_size)
|
||||
{
|
||||
PyErr_SetObject(PyExc_KeyError, BOOST_PYTHON_CONVERSION::to_python(index));
|
||||
throw python::error_already_set();
|
||||
}
|
||||
|
||||
int delta = index - m_index;
|
||||
if(delta < 0)
|
||||
{
|
||||
m_iter = m_begin;
|
||||
delta = index;
|
||||
}
|
||||
std::advance(m_iter, delta);
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
void advance(int index, std::bidirectional_iterator_tag)
|
||||
{
|
||||
if(index < 0 || index >= m_size)
|
||||
{
|
||||
PyErr_SetObject(PyExc_KeyError, BOOST_PYTHON_CONVERSION::to_python(index));
|
||||
throw python::error_already_set();
|
||||
}
|
||||
int delta = index - m_index;
|
||||
std::advance(m_iter, delta);
|
||||
m_index = index;
|
||||
}
|
||||
|
||||
value_type const & get_item(int index)
|
||||
{
|
||||
advance(index, std::iterator_category(m_iter));
|
||||
return *m_iter;
|
||||
}
|
||||
|
||||
void set_item(int index, value_type const & v)
|
||||
{
|
||||
advance(index, std::iterator_category(m_iter));
|
||||
*m_iter = v;
|
||||
}
|
||||
|
||||
int len() const
|
||||
{ return m_size; }
|
||||
|
||||
ref m_python_object;
|
||||
iterator m_begin, m_iter;
|
||||
int m_index, m_size;
|
||||
};
|
||||
|
||||
/* create a cursor for an STL conforming container */
|
||||
template <class T>
|
||||
struct extension_class_cursor_factory
|
||||
{
|
||||
static cursor<T> get(ref container)
|
||||
{
|
||||
return cursor<T>(
|
||||
BOOST_PYTHON_CONVERSION::from_python(container.get(), type<T&>()),
|
||||
container);
|
||||
}
|
||||
};
|
||||
|
||||
template <class From, class To>
|
||||
struct define_conversion
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdio.h> // used for portability on broken compilers
|
||||
#include <math.h> // for pow()
|
||||
#include <boost/rational.hpp>
|
||||
#include <list>
|
||||
|
||||
namespace bpl_test {
|
||||
|
||||
@@ -1009,6 +1010,15 @@ void init_module(boost::python::module_builder& m)
|
||||
// export non-operator function as heterogeneous reverse-argument operator
|
||||
int_class.def(&rmul, "__rmul__");
|
||||
|
||||
// wrap an STL conforming container
|
||||
boost::python::class_builder<std::list<Int> > intlist_class(m, "IntList");
|
||||
|
||||
intlist_class.def(boost::python::constructor<>());
|
||||
intlist_class.def((void (std::list<Int>::*)(Int const &))
|
||||
&std::list<Int>::push_back, "append");
|
||||
|
||||
// wrap the iterator of an STL conforming container in a cursor
|
||||
m.def_cursor_for(intlist_class);
|
||||
|
||||
boost::python::class_builder<EnumOwner> enum_owner(m, "EnumOwner");
|
||||
enum_owner.def(boost::python::constructor<EnumOwner::enum_type, const EnumOwner::enum_type&>());
|
||||
|
||||
@@ -1029,6 +1029,30 @@ Test operator export to a subclass
|
||||
>>> j.i()
|
||||
15
|
||||
|
||||
========= Test creation of a cursor for an STL conforming container ==========
|
||||
|
||||
>>> i1 = Int(1)
|
||||
>>> i2 = Int(2)
|
||||
>>> i3 = Int(3)
|
||||
>>> l = IntList()
|
||||
>>> l.append(i1)
|
||||
>>> l.append(i2)
|
||||
>>> l.append(i3)
|
||||
>>> for i in l.cursor():
|
||||
... print i.i()
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
test that cursor keeps a reference to its container
|
||||
|
||||
>>> c = l.cursor()
|
||||
>>> del l
|
||||
>>> for i in c:
|
||||
... print i.i()
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
========= Prove that the "phantom base class" issue is resolved ==========
|
||||
|
||||
|
||||
Reference in New Issue
Block a user