Exposes the extended slicing protocol by wrapping the built-in slice
+type. The semantics of the constructors and member functions defined
+below can be fully understood by reading the TypeWrapper concept
+definition. Since slice is publicly derived from object, the public object
+interface applies to slice instances as well.
+
+
Class slice synopsis
+
+namespace boost { namespace python
+{
+ class slice : public object
+ {
+ public:
+ slice(); // create an empty slice, equivalent to [::]
+
+ template <typename Int1, typename Int2>
+ slice(Int1 start, Int2 stop);
+
+ template <typename Int1, typename Int2, typename Int3>
+ slice(Int1 start, Int2 stop, Int3 step);
+
+ // Access the parameters this slice was created with.
+ object start();
+ object stop();
+ object step();
+
+ // The return type of slice::get_indicies()
+ template <typename RandomAccessIterator>
+ struct range
+ {
+ RandomAccessIterator start;
+ RandomAccessIterator stop;
+ int step;
+ };
+
+ template <typename RandomAccessIterator>
+ range<RandomAccessIterator>
+ get_indicies(
+ RandomAccessIterator const& begin,
+ RandomAccessIterator const& end);
+ };
+}}
+
+
Class slice
+constructors
+
+
slice();
+
+
Effects: constructs a slice with default stop, start, and
+step values. Equivalent to the slice object created by the Python
+expression base[::].
Requires:start, stop, and step
+are of type slice_nil or convertible to type object.
+
Effects: constructs a new slice with default step value
+and the provided start and stop values. Equivalent to the slice
+object
+created by the built-in Python function slice(start,stop),
+or the Python expression base[start:stop].
+
Throws:error_already_set and sets a Python TypeError
+exception if no conversion is possible from the arguments to type object.
Requires:start, stop, and step are integers, slice_nil, or convertible to type object.
+
Effects: constructs a new slice with start stop and step
+values. Equivalent to the slice object created
+by the built-in Python function slice(start,stop,step),
+or the Python expression base[start:stop:step].
+
Throws:error_already_set and sets a Python TypeError
+exception if no conversion is possible from the arguments to type
+object.
Returns:the parameter that
+the slice was created with. If the parameter was omitted or
+slice_nil was used when the slice was created, than that parameter will
+be a reference to PyNone and compare equal to a default-constructed
+object. In principal, any object may be used when creating a
+slice object, but in practice they are usually integers.
Arguments: A pair of STL-conforming Random Access
+Iterators that form a half-open range.
+
Effects: Create a RandomAccessIterator pair that defines a
+fully-closed range within the [begin,end) range of its arguments.
+This function translates this slice's indicies while accounting for the
+effects of any PyNone or negative indicies, and non-singular step sizes.
+
Returns: a slice::range
+that has been initialized with a non-zero value of step and a pair of
+RandomAccessIterators that point within the range of this functions
+arguments and define a closed interval.
+
Throws:Raises a Python TypeError exception if any of this slice's arguments
+are neither references to PyNone nor convertible to int. Throws
+std::invalid_argument if the resulting range would be empty. You
+should always wrap calls to slice::get_indicies()
+within try { ...; } catch (std::invalid_argument) {} to
+handle this case and take appropriate action.
+
Rationale: closed-interval: If
+an open interval were used, then for step
+size other than 1, the required state for the end iterator would point
+beyond the one-past-the-end position or before the beginning of the
+specified range.
+exceptions on empty slice: It is impossible to define a closed interval
+over an empty range, so some other form of error checking would have to
+be used to prevent undefined behavior. In the case where the
+exception is not caught, it will simply be translated to Python by the
+default exception handling mechanisms.
+
+
Examples
+
+using namespace boost::python;
+
+// Perform an extended slice of a Python list.
+// Warning: extended slicing was not supported for built-in types prior
+// to Python 2.3
+list odd_elements(list l)
+{
+ return l[slice(_,_,2)];
+}
+
+// Perform a multidimensional rich slice of a Numeric.array
+numeric::array even_columns(numeric::array arr)
+{
+ // select every other column, starting with the second, of a 2-D array.
+ // Equivalent to "return arr[:, 1::2]" in Python.
+ return arr[make_tuple( slice(), slice(1,_,2))];
+}
+
+// Perform a summation over a slice of a std::vector.
+double partial_sum(std::vector<double> const& Foo, slice index)
+{
+ slice::range<std::vector<double>::const_iterator> bounds;
+ try {
+ bounds = index.get_indicies<>(Foo.begin(), Foo.end());
+ }
+ catch (std::invalid_argument) {
+ return 0.0;
+ }
+ double sum = 0.0;
+ while (bounds.start != bounds.end) {
+ sum += *bounds.start;
+ std::advance( bounds.start, bounds.step);
+ }
+ sum += *bounds.start;
+ return sum;
+}
+
+
+
diff --git a/include/boost/python/object_core.hpp b/include/boost/python/object_core.hpp
index 667fe4d9..eb37f71f 100755
--- a/include/boost/python/object_core.hpp
+++ b/include/boost/python/object_core.hpp
@@ -13,7 +13,6 @@
# include
# include
# include
-# include
# include
# include
# include
@@ -63,6 +62,7 @@ namespace api
struct item_policies;
struct const_slice_policies;
struct slice_policies;
+ class slice_nil;
typedef proxy const_object_attribute;
typedef proxy object_attribute;
@@ -471,4 +471,6 @@ inline PyObject* get_managed_object(object const& x, tag_t)
}} // namespace boost::python
+# include
+
#endif // OBJECT_CORE_DWA2002615_HPP
diff --git a/include/boost/python/slice.hpp b/include/boost/python/slice.hpp
new file mode 100644
index 00000000..9b05a941
--- /dev/null
+++ b/include/boost/python/slice.hpp
@@ -0,0 +1,251 @@
+#ifndef BOOST_PYTHON_SLICE_JDB20040105_HPP
+#define BOOST_PYTHON_SLICE_JDB20040105_HPP
+
+// Copyright (c) 2004 Jonathan Brandmeyer
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#include
+#include
+
+#include
+#include
+
+namespace boost { namespace python {
+
+class slice : public object
+{
+ public:
+ // Equivalent to slice(::)
+ slice();
+
+ // Each argument must be int, slice_nil, or implicitly convertable to int
+ template
+ slice( Integer1 start, Integer2 stop)
+ : object( boost::python::detail::new_reference(
+ PySlice_New( object(start).ptr(), object(stop).ptr(), NULL)))
+ {}
+
+ template
+ slice( Integer1 start, Integer2 stop, Integer3 stride)
+ : object( boost::python::detail::new_reference(
+ PySlice_New( object(start).ptr(), object(stop).ptr(),
+ object(stride).ptr())))
+ {}
+
+ // Get the Python objects associated with the slice. In principle, these
+ // may be any arbitrary Python type, but in practice they are usually
+ // integers. If one or more parameter is ommited in the Python expression
+ // that created this slice, than that parameter is None here, and compares
+ // equal to a default-constructed boost::python::object.
+ // If a user-defined type wishes to support slicing, then support for the
+ // special meaning associated with negative indicies is up to the user.
+ object start();
+ object stop();
+ object step();
+
+ // The following algorithm is intended to automate the process of
+ // determining a slice range when you want to fully support negative
+ // indicies and non-singular step sizes. Its functionallity is simmilar to
+ // PySlice_GetIndicesEx() in the Python/C API, but tailored for C++ users.
+ // This template returns a slice::range struct that, when used in the
+ // following iterative loop, will traverse a slice of the function's
+ // arguments.
+ // while (start != end) {
+ // do_foo(...);
+ // std::advance( start, step);
+ // }
+ // do_foo(...); // repeat exactly once more.
+
+ // Arguments: a [begin, end) pair of STL-conforming random-access iterators.
+
+ // Return: slice::range, where start and stop define a _closed_ interval
+ // that covers at most [begin, end-1] of the provided arguments, and a step
+ // that is non-zero.
+
+ // Throws: error_already_set() if any of the indices are neither None nor
+ // integers, or the slice has a step value of zero.
+ // std::invalid_argument if the resulting range would be empty. Normally,
+ // you should catch this exception and return an empty sequence of the
+ // appropriate type.
+
+ // Performance: constant time for random-access iterators.
+
+ // Rationale:
+ // closed-interval: If an open interval were used, then for a non-singular
+ // value for step, the required state for the end iterator could be
+ // beyond the one-past-the-end postion of the specified range. While
+ // probably harmless, the behavior of STL-conforming iterators is
+ // undefined in this case.
+ // exceptions on zero-length range: It is impossible to define a closed
+ // interval over an empty range, so some other form of error checking
+ // would have to be used by the user to prevent undefined behavior. In
+ // the case where the user fails to catch the exception, it will simply
+ // be translated to Python by the default exception handling mechanisms.
+
+ #ifndef BOOST_NO_MEMBER_TEMPLATES
+
+ template
+ struct range
+ {
+ RandomAccessIterator start;
+ RandomAccessIterator stop;
+ int step;
+ };
+
+ template
+ range
+ get_indicies( const RandomAccessIterator& begin,
+ const RandomAccessIterator& end)
+ {
+ // This is based loosely on PySlice_GetIndicesEx(), but it has been
+ // carefully crafted to ensure that these iterators never fall out of
+ // the range of the container.
+ slice::range ret;
+ typename RandomAccessIterator::difference_type max_dist =
+ std::distance( begin, end);
+
+ object slice_start = this->start();
+ object slice_stop = this->stop();
+ object slice_step = this->step();
+
+ // Extract the step.
+ if (slice_step == object()) {
+ ret.step = 1;
+ }
+ else {
+ ret.step = extract( slice_step);
+ if (ret.step == 0) {
+ PyErr_SetString( PyExc_IndexError, "step size cannot be zero.");
+ throw_error_already_set();
+ }
+ }
+
+ // Setup the start iterator.
+ if (slice_start == object()) {
+ if (ret.step < 0) {
+ ret.start = end;
+ --ret.start;
+ }
+ else
+ ret.start = begin;
+ }
+ else {
+ int i = extract( slice_start);
+ if (i >= max_dist && ret.step > 0)
+ throw std::invalid_argument( "Zero-length slice");
+ if (i >= 0) {
+ ret.start = begin;
+ std::advance( ret.start, std::min(i, max_dist-1));
+ }
+ else {
+ if (i < -max_dist && ret.step < 0)
+ throw std::invalid_argument( "Zero-length slice");
+ ret.start = end;
+ // Advance start (towards begin) not farther than begin.
+ std::advance( ret.start, (-i < max_dist) ? i : -max_dist );
+ }
+ }
+
+ // Set up the stop iterator. This one is a little trickier since slices
+ // define a [) range, and we are returning a [] range.
+ if (slice_stop == object()) {
+ if (ret.step < 0) {
+ ret.stop = begin;
+ }
+ else {
+ ret.stop = end;
+ std::advance( ret.stop, -1);
+ }
+ }
+ else {
+ int i = extract( slice_stop);
+ // First, branch on which direction we are going with this.
+ if (ret.step < 0) {
+ if (i+1 >= max_dist || i == -1)
+ throw std::invalid_argument( "Zero-length slice");
+
+ if (i >= 0) {
+ ret.stop = begin;
+ std::advance( ret.stop, i+1);
+ }
+ else { // i is negative, but more negative than -1.
+ ret.stop = end;
+ std::advance( ret.stop, (-i < max_dist) ? i : -max_dist);
+ }
+ }
+ else { // stepping forward
+ if (i == 0 || -i >= max_dist)
+ throw std::invalid_argument( "Zero-length slice");
+
+ if (i > 0) {
+ ret.stop = begin;
+ std::advance( ret.stop, std::min( i-1, max_dist-1));
+ }
+ else { // i is negative, but not more negative than -max_dist
+ ret.stop = end;
+ std::advance( ret.stop, i-1);
+ }
+ }
+ }
+
+ // Now the fun part, handling the possibilites surrounding step.
+ // At this point, step has been initialized, ret.stop, and ret.step
+ // represent the widest possible range that could be traveled
+ // (inclusive), and final_dist is the maximum distance covered by the
+ // slice.
+ typename RandomAccessIterator::difference_type final_dist =
+ std::distance( ret.start, ret.stop);
+
+ // First case, if both ret.start and ret.stop are equal, then step
+ // is irrelevant and we can return here.
+ if (final_dist == 0)
+ return ret;
+
+ // Second, if there is a sign mismatch, than the resulting range and
+ // step size conflict: std::advance( ret.start, ret.step) goes away from
+ // ret.stop.
+ if ((final_dist > 0) != (ret.step > 0))
+ throw std::invalid_argument( "Zero-length slice.");
+
+ // Finally, if the last step puts us past the end, we move ret.stop
+ // towards ret.start in the amount of the remainder.
+ // I don't remember all of the oolies surrounding negative modulii,
+ // so I am handling each of these cases separately.
+ if (final_dist < 0) {
+ int remainder = -final_dist % -ret.step;
+ std::advance( ret.stop, remainder);
+ }
+ else {
+ int remainder = final_dist % ret.step;
+ std::advance( ret.stop, -remainder);
+ }
+
+ return ret;
+ }
+ #endif // !defined BOOST_NO_MEMBER_TEMPLATES
+
+ public:
+ // This declaration, in conjunction with the specialization of
+ // object_manager_traits<> below, allows C++ functions accepting slice
+ // arguments to be called from from Python. These constructors should never
+ // be used in client code.
+ BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice, object)
+};
+
+
+namespace converter {
+
+template<>
+struct object_manager_traits
+ : pytype_object_manager_traits<&PySlice_Type, slice>
+{
+};
+
+} // !namesapce converter
+
+} } // !namespace ::boost::python
+
+
+#endif // !defined BOOST_PYTHON_SLICE_JDB20040105_HPP
diff --git a/include/boost/python/slice_nil.hpp b/include/boost/python/slice_nil.hpp
index b77d06cb..cca21f96 100644
--- a/include/boost/python/slice_nil.hpp
+++ b/include/boost/python/slice_nil.hpp
@@ -7,18 +7,20 @@
# define SLICE_NIL_DWA2002620_HPP
# include
+# include
namespace boost { namespace python { namespace api {
-class object;
-
-enum slice_nil
+class slice_nil : public object
{
-# ifndef _ // Watch out for GNU gettext users, who #define _(x)
- _
-# endif
+ public:
+ slice_nil() : object() {}
};
+# ifndef _ // Watch out for GNU gettext users, who #define _(x)
+static const slice_nil _ = slice_nil();
+# endif
+
template
struct slice_bound
{
diff --git a/src/slice.cpp b/src/slice.cpp
new file mode 100644
index 00000000..273c8924
--- /dev/null
+++ b/src/slice.cpp
@@ -0,0 +1,38 @@
+#include "boost/python/slice.hpp"
+
+// Copyright (c) 2004 Jonathan Brandmeyer
+// Use, modification and distribution are subject to the
+// Boost Software License, Version 1.0. (See accompanying file
+// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+
+namespace boost { namespace python {
+
+slice::slice()
+ : object( boost::python::detail::new_reference(
+ PySlice_New( NULL, NULL, NULL)))
+{
+}
+
+object
+slice::start()
+{
+ return object( detail::borrowed_reference(
+ ((PySliceObject*)this->ptr())->start));
+}
+
+object
+slice::stop()
+{
+ return object( detail::borrowed_reference(
+ ((PySliceObject*)this->ptr())->stop));
+}
+
+object
+slice::step()
+{
+ return object( detail::borrowed_reference(
+ ((PySliceObject*)this->ptr())->step));
+}
+
+} } // !namespace boost::python
diff --git a/test/Jamfile b/test/Jamfile
index e53db7cb..a8a1c67b 100644
--- a/test/Jamfile
+++ b/test/Jamfile
@@ -124,6 +124,7 @@ bpl-test crossmod_exception
[ bpl-test dict ]
[ bpl-test tuple ]
[ bpl-test str ]
+[ bpl-test slice ]
[ bpl-test virtual_functions ]
[ bpl-test back_reference ]
@@ -194,4 +195,4 @@ bpl-test crossmod_exception
[ compile-fail ./as_to_python_function.cpp py-unit-test ]
[ compile-fail ./object_fail1.cpp py-unit-test ]
;
-}
\ No newline at end of file
+}
diff --git a/test/slice.cpp b/test/slice.cpp
new file mode 100644
index 00000000..99b633cd
--- /dev/null
+++ b/test/slice.cpp
@@ -0,0 +1,83 @@
+#include
+#include
+
+using namespace boost::python;
+
+// These checks are only valid under Python 2.3
+// (rich slicing wasn't supported for builtins under Python 2.2)
+bool check_string_rich_slice()
+{
+ object s("hello, world");
+
+ // default slice
+ if (s[slice()] != "hello, world")
+ return false;
+
+ // simple reverse
+ if (s[slice(_,_,-1)] != "dlrow ,olleh")
+ return false;
+
+ // reverse with mixed-sign offsets
+ if (s[slice(-6,1,-1)] != " ,oll")
+ return false;
+
+ // all of the object.cpp check_string_slice() checks should work
+ // with the form that omits the step argument.
+ if (s[slice(_,-3)] != "hello, wo")
+ return false;
+ if (s[slice(-3,_)] != "rld")
+ return false;
+ if (", " != s[slice(5,7)])
+ return false;
+
+ return s[slice(2,-1)][slice(1,-1)] == "lo, wor";
+}
+
+// These tests work with Python 2.2, but you must have Numeric installed.
+bool check_numeric_array_rich_slice()
+{
+ using numeric::array;
+ object original = array( make_tuple( make_tuple( 11, 12, 13, 14),
+ make_tuple( 21, 22, 23, 24),
+ make_tuple( 31, 32, 33, 34),
+ make_tuple( 41, 42, 43, 44)));
+ object upper_left_quadrant = array( make_tuple( make_tuple( 11, 12),
+ make_tuple( 21, 22)));
+ object odd_cells = array( make_tuple( make_tuple( 11, 13),
+ make_tuple( 31, 33)));
+ object even_cells = array( make_tuple( make_tuple( 22, 24),
+ make_tuple( 42, 44)));
+ object lower_right_quadrant_reversed = array(
+ make_tuple( make_tuple(44, 43),
+ make_tuple(34, 33)));
+
+ // The following comments represent equivalent Python expressions used
+ // to validate the array behavior.
+ // original[::] == original
+ if (original[slice()] != original)
+ return false;
+ // original[:2,:2] == array( [[11, 12], [21, 22]])
+ if (original[make_tuple(slice(_,2), slice(_,2))] != upper_left_quadrant)
+ return false;
+ // original[::2,::2] == array( [[11, 13], [31, 33]])
+ if (original[make_tuple( slice(_,_,2), slice(_,_,2))] != odd_cells)
+ return false;
+ // original[1::2, 1::2] == array( [[22, 24], [42, 44]])
+ if (original[make_tuple( slice(1,_,2), slice(1,_,2))] != even_cells)
+ return false;
+ // original[:-3:-1, :-3,-1] == array( [[44, 43], [34, 33]])
+ if (original[make_tuple( slice(_,-3,-1), slice(_,-3,-1))] != lower_right_quadrant_reversed)
+ return false;
+
+ return true;
+}
+
+// Verify functions accepting a slice argument can be called
+bool accept_slice( slice) { return true; }
+
+BOOST_PYTHON_MODULE(slice_ext)
+{
+ def( "accept_slice", accept_slice);
+ def( "check_numeric_array_rich_slice", check_numeric_array_rich_slice);
+ def( "check_string_rich_slice", check_string_rich_slice);
+}
diff --git a/test/slice.py b/test/slice.py
new file mode 100644
index 00000000..b3c2dbcd
--- /dev/null
+++ b/test/slice.py
@@ -0,0 +1,42 @@
+"""
+>>> from slice_ext import *
+>>> accept_slice(slice(1, None, (1,2)))
+1
+>>> try:
+... accept_slice(list((1,2)))
+... print "test failed"
+... except:
+... print "test passed"
+...
+test passed
+>>> check_numeric_array_rich_slice()
+1
+>>> import sys
+>>> if sys.version_info[0] == 2 and sys.version_info[1] >= 3:
+... check_string_rich_slice()
+... elif sys.version_info[0] > 2:
+... check_string_rich_slice()
+... else:
+... print 1
+...
+1
+"""
+
+# Performs an affirmative and negative argument resolution check,
+# checks the operation of extended slicing in Numeric.array's
+# checks the operation of extended slicing in new strings (Python 2.3 only).
+
+def run(args = None):
+ import sys
+ import doctest
+
+ if args is not None:
+ sys.argv = args
+ return doctest.testmod(sys.modules.get(__name__))
+
+if __name__ == '__main__':
+ print "running..."
+ import sys
+ status = run()[0]
+ if (status == 0): print "Done."
+ sys.exit(status)