Skip to content

Commit b22efa8

Browse files
authored
Merge pull request #592 from cmastalli/topic/slide-vector
Add support for Python slice objects for `std::vector`
2 parents 4530e62 + 69d3949 commit b22efa8

File tree

3 files changed

+104
-4
lines changed

3 files changed

+104
-4
lines changed

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
77
## [Unreleased]
88

99
### Changed
10-
1110
- Remove `accelerate.hpp` header that clash with Accelerate.hpp in non case sensitive OS ([#593](https://github.com/stack-of-tasks/eigenpy/pull/593)
12-
We don't consider it an API break since this header was rarely used.
11+
We don't consider it an API break since this header was rarely used.
12+
13+
### Added
14+
- Support for Python slice, tuple and list indexing for `std::vector` bindings ([#592](https://github.com/stack-of-tasks/eigenpy/pull/592))
1315

1416
## [3.12.0] - 2025-08-12
1517

include/eigenpy/std-vector.hpp

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
///
22
/// Copyright (c) 2016-2024 CNRS INRIA
3+
/// Copyright (c) 2025-2025 Heriot-Watt University
34
/// This file was taken from Pinocchio (header
45
/// <pinocchio/bindings/python/utils/std-vector.hpp>)
56
///
@@ -84,11 +85,14 @@ struct overload_base_get_item_for_std_vector
8485

8586
template <class Class>
8687
void visit(Class &cl) const {
87-
cl.def("__getitem__", &base_get_item);
88+
cl.def("__getitem__", &base_get_item_int)
89+
.def("__getitem__", &base_get_item_slice)
90+
.def("__getitem__", &base_get_item_list)
91+
.def("__getitem__", &base_get_item_tuple);
8892
}
8993

9094
private:
91-
static boost::python::object base_get_item(
95+
static boost::python::object base_get_item_int(
9296
boost::python::back_reference<Container &> container, PyObject *i_) {
9397
index_type idx = convert_index(container.get(), i_);
9498
typename Container::iterator i = container.get().begin();
@@ -104,6 +108,83 @@ struct overload_base_get_item_for_std_vector
104108
return bp::object(bp::handle<>(convert(*i)));
105109
}
106110

111+
static boost::python::object base_get_item_slice(
112+
boost::python::back_reference<Container &> container,
113+
boost::python::slice slice) {
114+
bp::list out;
115+
try {
116+
auto rng =
117+
slice.get_indices(container.get().begin(), container.get().end());
118+
// rng.start, rng.stop are iterators; rng.step is int; [start, stop] is
119+
// closed
120+
typename bp::to_python_indirect<value_type &,
121+
bp::detail::make_reference_holder>
122+
convert;
123+
// forward or backward
124+
for (typename Container::iterator it = rng.start;;
125+
std::advance(it, rng.step)) {
126+
out.append(bp::object(bp::handle<>(convert(*it))));
127+
if (it == rng.stop) break; // closed interval, include stop
128+
}
129+
} catch (const std::invalid_argument &) {
130+
// Boost.Python specifies empty ranges throw invalid_argument.
131+
// Return [] (matches Python's behavior for empty slices).
132+
return bp::list();
133+
}
134+
return out;
135+
}
136+
137+
static bp::object base_get_item_list(bp::back_reference<Container &> c,
138+
bp::list idxs) {
139+
const Py_ssize_t m = bp::len(idxs);
140+
bp::list out;
141+
for (Py_ssize_t k = 0; k < m; ++k) {
142+
bp::object obj = idxs[k];
143+
bp::extract<long> ei(obj);
144+
if (!ei.check()) {
145+
PyErr_SetString(PyExc_TypeError, "indices must be integers");
146+
bp::throw_error_already_set();
147+
}
148+
auto idx = normalize_index(c.get().size(), ei());
149+
out.append(elem_ref(c.get(), idx));
150+
}
151+
return out;
152+
}
153+
154+
static bp::object base_get_item_tuple(bp::back_reference<Container &> c,
155+
bp::tuple idxs) {
156+
const Py_ssize_t m = bp::len(idxs);
157+
bp::list out;
158+
for (Py_ssize_t k = 0; k < m; ++k) {
159+
bp::object obj = idxs[k];
160+
bp::extract<long> ei(obj);
161+
if (!ei.check()) {
162+
PyErr_SetString(PyExc_TypeError, "indices must be integers");
163+
bp::throw_error_already_set();
164+
}
165+
auto idx = normalize_index(c.get().size(), ei());
166+
out.append(elem_ref(c.get(), idx));
167+
}
168+
return out;
169+
}
170+
171+
static index_type normalize_index(std::size_t n, long i) {
172+
long idx = i;
173+
if (idx < 0) idx += static_cast<long>(n);
174+
if (idx < 0 || idx >= static_cast<long>(n)) {
175+
PyErr_SetString(PyExc_IndexError, "index out of range");
176+
bp::throw_error_already_set();
177+
}
178+
return static_cast<index_type>(idx);
179+
}
180+
181+
static bp::object elem_ref(Container &c, index_type i) {
182+
typename bp::to_python_indirect<value_type &,
183+
bp::detail::make_reference_holder>
184+
conv;
185+
return bp::object(bp::handle<>(conv(c[i])));
186+
}
187+
107188
static index_type convert_index(Container &container, PyObject *i_) {
108189
bp::extract<long> i(i_);
109190
if (i.check()) {

unittest/python/test_std_vector.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,20 @@ def checkZero(v):
9898
# Test mutable __getitem__
9999
l5[0][:] = 0.0
100100
assert np.allclose(l5[0], 0.0)
101+
102+
# Test slicing
103+
l6 = eigenpy.StdVec_VectorXd()
104+
for _ in range(4):
105+
l6.append(rng.random(3))
106+
checkAllValues(l6[:1], l6.tolist()[:1])
107+
checkAllValues(l6[1:], l6.tolist()[1:])
108+
checkAllValues(l6[:-1], l6.tolist()[:-1])
109+
checkAllValues(l6[::2], l6.tolist()[::2])
110+
L = [0, 2]
111+
L6_copy = l6[L]
112+
for k, i in enumerate(L):
113+
checkAllValues(L6_copy[k], l6[i])
114+
T = (0, 2)
115+
L6_copy = l6[T]
116+
for k, i in enumerate(L):
117+
checkAllValues(L6_copy[k], l6[i])

0 commit comments

Comments
 (0)