2
0
mirror of https://github.com/boostorg/python.git synced 2026-01-20 16:52:15 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Ullrich Köthe
f6b99ede25 added the cursor feature
[SVN r8368]
2000-11-30 16:13:12 +00:00
Ullrich Köthe
7e672720f1 added cursor feature (including tests) and updated documentation
[SVN r8367]
2000-11-30 16:11:59 +00:00
7 changed files with 332 additions and 6 deletions

View File

@@ -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&lt;BigNum&gt;</code>. First, we have to wrap this class itself,
as usual:
<blockquote><pre>
// wrap an STL conforming container
boost::python::class_builder&lt;std::list&lt;BigNum&gt; &gt; bignum_list_class(my_module, "BigNumList");
bignum_list_class.def(boost::python::constructor&lt;&gt;());
bignum_list_class.def((void (std::list&lt;BigNum&gt;::*)(BigNum const &amp;))
&amp;std::list&lt;BigNum&gt;::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">

View File

@@ -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

View File

@@ -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
{

View File

@@ -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();

View File

@@ -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
{

View File

@@ -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&>());

View File

@@ -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 ==========