From 7e672720f18dcaf568a47b04e5ef46d4b0d41e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20K=C3=B6the?= Date: Thu, 30 Nov 2000 16:11:59 +0000 Subject: [PATCH] added cursor feature (including tests) and updated documentation [SVN r8367] --- doc/special.html | 78 ++++++++++++++++++++++++++++++++--- src/gen_extclass.py | 92 ++++++++++++++++++++++++++++++++++++++++++ test/comprehensive.cpp | 10 +++++ test/comprehensive.py | 24 +++++++++++ 4 files changed, 199 insertions(+), 5 deletions(-) diff --git a/doc/special.html b/doc/special.html index 7debc7c1..73e96c26 100644 --- a/doc/special.html +++ b/doc/special.html @@ -657,7 +657,7 @@ for i in S: for (iterator i = S.begin(), end = S.end(); i != end; ++i) -

One could try to wrap C++ iterators in order to carry the C++ idiom into +

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

    @@ -671,10 +671,78 @@ for (iterator i = S.begin(), end = S.end(); i != end; ++i)

- It is a better idea to support the standard Python - sequence and mapping protocols 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 + std::list<BigNum>. First, we have to wrap this class itself, + as usual: + +

+// 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");
+
+ + Since std::list conforms to the requirements + of an STL container, we may create a cursor for it: + +
+// define cursor for an STL conforming container
+my_module.def_cursor_for(bignum_list_class);
+
+ + This enables the following use of BigNumList within Python: + +
+>>> 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
+
+ + 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 [0, cursor.len()-1]. You can always + access items in any order: + +
+>>> 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
+
+ + 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. + +

+ An alternative way of wrapping your containers is to support + Python's standard + sequence and mapping protocols. 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 diff --git a/src/gen_extclass.py b/src/gen_extclass.py index f8906970..b7fef7f8 100644 --- a/src/gen_extclass.py +++ b/src/gen_extclass.py @@ -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 +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 +struct extension_class_cursor_factory +{ + static cursor get(ref container) + { + return cursor( + BOOST_PYTHON_CONVERSION::from_python(container.get(), type()), + container); + } +}; + template struct define_conversion { diff --git a/test/comprehensive.cpp b/test/comprehensive.cpp index 7fad74bb..2ac3a092 100644 --- a/test/comprehensive.cpp +++ b/test/comprehensive.cpp @@ -10,6 +10,7 @@ #include // used for portability on broken compilers #include // for pow() #include +#include 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 > intlist_class(m, "IntList"); + + intlist_class.def(boost::python::constructor<>()); + intlist_class.def((void (std::list::*)(Int const &)) + &std::list::push_back, "append"); + + // wrap the iterator of an STL conforming container in a cursor + m.def_cursor_for(intlist_class); boost::python::class_builder enum_owner(m, "EnumOwner"); enum_owner.def(boost::python::constructor()); diff --git a/test/comprehensive.py b/test/comprehensive.py index a8e181d2..92fb8f86 100644 --- a/test/comprehensive.py +++ b/test/comprehensive.py @@ -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 ==========