Skip to content

Commit 5d976d3

Browse files
committed
Map enhancements: split GenericMapPythonVisitor into its own file map.hpp, add visitor arg to ::expose() with overloads
1 parent ebad541 commit 5d976d3

File tree

6 files changed

+286
-226
lines changed

6 files changed

+286
-226
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- `GenericMapPythonVisitor`/`StdMapPythonVisitor` can now take an extra visitor argument in the `expose()` method, similar to `StdVectorPythonVisitor`
12+
13+
### Changed
14+
15+
- Move `GenericMapPythonVisitor` to its own header `eigenpy/map.hpp`
16+
- Move `EmptyPythonVisitor` to new header `eigenpy/utils/empty-visitor.hpp`
17+
918
## [3.9.1] - 2024-09-19
1019

1120
### Added

include/eigenpy/map.hpp

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/// Copyright (c) 2016-2024 CNRS INRIA
2+
/// This file was originally taken from Pinocchio (header
3+
/// <pinocchio/bindings/python/utils/std-vector.hpp>)
4+
///
5+
6+
#ifndef __eigenpy_map_hpp__
7+
#define __eigenpy_map_hpp__
8+
9+
#include "eigenpy/pickle-vector.hpp"
10+
#include "eigenpy/registration.hpp"
11+
#include "eigenpy/utils/empty-visitor.hpp"
12+
13+
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
14+
#include <boost/python/stl_iterator.hpp>
15+
#include <boost/python/to_python_converter.hpp>
16+
17+
namespace eigenpy {
18+
namespace details {
19+
20+
/// \brief Change the behavior of indexing (method __getitem__ in Python).
21+
/// This is suitable e.g. for container of Eigen matrix objects if you want to
22+
/// mutate them.
23+
/// \sa overload_base_get_item_for_std_vector
24+
template <typename Container>
25+
struct overload_base_get_item_for_std_map
26+
: public boost::python::def_visitor<
27+
overload_base_get_item_for_std_map<Container> > {
28+
typedef typename Container::value_type value_type;
29+
typedef typename Container::value_type::second_type data_type;
30+
typedef typename Container::key_type key_type;
31+
typedef typename Container::key_type index_type;
32+
33+
template <class Class>
34+
void visit(Class& cl) const {
35+
cl.def("__getitem__", &base_get_item);
36+
}
37+
38+
private:
39+
static boost::python::object base_get_item(
40+
boost::python::back_reference<Container&> container, PyObject* i_) {
41+
index_type idx = convert_index(container.get(), i_);
42+
typename Container::iterator i = container.get().find(idx);
43+
if (i == container.get().end()) {
44+
PyErr_SetString(PyExc_KeyError, "Invalid key");
45+
boost::python::throw_error_already_set();
46+
}
47+
48+
typename boost::python::to_python_indirect<
49+
data_type&, boost::python::detail::make_reference_holder>
50+
convert;
51+
return boost::python::object(boost::python::handle<>(convert(i->second)));
52+
}
53+
54+
static index_type convert_index(Container& /*container*/, PyObject* i_) {
55+
boost::python::extract<key_type const&> i(i_);
56+
if (i.check()) {
57+
return i();
58+
} else {
59+
boost::python::extract<key_type> i(i_);
60+
if (i.check()) return i();
61+
}
62+
63+
PyErr_SetString(PyExc_TypeError, "Invalid index type");
64+
boost::python::throw_error_already_set();
65+
return index_type();
66+
}
67+
};
68+
69+
} // namespace details
70+
71+
///////////////////////////////////////////////////////////////////////////////
72+
// The following snippet of code has been taken from the header
73+
// https://github.com/loco-3d/crocoddyl/blob/v2.1.0/bindings/python/crocoddyl/utils/map-converter.hpp
74+
// The Crocoddyl library is written by Carlos Mastalli, Nicolas Mansard and
75+
// Rohan Budhiraja.
76+
///////////////////////////////////////////////////////////////////////////////
77+
78+
namespace bp = boost::python;
79+
80+
/**
81+
* @brief Create a pickle interface for the std::map
82+
*
83+
* @param[in] Container Map type to be pickled
84+
* \sa Pickle
85+
*/
86+
template <typename Container>
87+
struct PickleMap : public PickleVector<Container> {
88+
static void setstate(bp::object op, bp::tuple tup) {
89+
Container& o = bp::extract<Container&>(op)();
90+
bp::stl_input_iterator<typename Container::value_type> begin(tup[0]), end;
91+
o.insert(begin, end);
92+
}
93+
};
94+
95+
/// Conversion from dict to map solution proposed in
96+
/// https://stackoverflow.com/questions/6116345/boostpython-possible-to-automatically-convert-from-dict-stdmap
97+
/// This template encapsulates the conversion machinery.
98+
template <typename Container>
99+
struct dict_to_map {
100+
static void register_converter() {
101+
bp::converter::registry::push_back(&dict_to_map::convertible,
102+
&dict_to_map::construct,
103+
bp::type_id<Container>());
104+
}
105+
106+
/// Check if conversion is possible
107+
static void* convertible(PyObject* object) {
108+
// Check if it is a list
109+
if (!PyObject_GetIter(object)) return 0;
110+
return object;
111+
}
112+
113+
/// Perform the conversion
114+
static void construct(PyObject* object,
115+
bp::converter::rvalue_from_python_stage1_data* data) {
116+
// convert the PyObject pointed to by `object` to a bp::dict
117+
bp::handle<> handle(bp::borrowed(object)); // "smart ptr"
118+
bp::dict dict(handle);
119+
120+
// get a pointer to memory into which we construct the map
121+
// this is provided by the Python runtime
122+
typedef bp::converter::rvalue_from_python_storage<Container> storage_type;
123+
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
124+
125+
// placement-new allocate the result
126+
new (storage) Container();
127+
128+
// iterate over the dictionary `dict`, fill up the map `map`
129+
Container& map(*(static_cast<Container*>(storage)));
130+
bp::list keys(dict.keys());
131+
int keycount(static_cast<int>(bp::len(keys)));
132+
for (int i = 0; i < keycount; ++i) {
133+
// get the key
134+
bp::object keyobj(keys[i]);
135+
bp::extract<typename Container::key_type> keyproxy(keyobj);
136+
if (!keyproxy.check()) {
137+
PyErr_SetString(PyExc_KeyError, "Bad key type");
138+
bp::throw_error_already_set();
139+
}
140+
typename Container::key_type key = keyproxy();
141+
142+
// get the corresponding value
143+
bp::object valobj(dict[keyobj]);
144+
bp::extract<typename Container::mapped_type> valproxy(valobj);
145+
if (!valproxy.check()) {
146+
PyErr_SetString(PyExc_ValueError, "Bad value type");
147+
bp::throw_error_already_set();
148+
}
149+
typename Container::mapped_type val = valproxy();
150+
map.emplace(key, val);
151+
}
152+
153+
// remember the location for later
154+
data->convertible = storage;
155+
}
156+
157+
static bp::dict todict(Container& self) {
158+
bp::dict dict;
159+
typename Container::const_iterator it;
160+
for (it = self.begin(); it != self.end(); ++it) {
161+
dict.setdefault(it->first, it->second);
162+
}
163+
return dict;
164+
}
165+
};
166+
167+
/// Policies which handle the non-default constructible case
168+
/// and set_item() using emplace().
169+
template <class Container, bool NoProxy>
170+
struct emplace_set_derived_policies
171+
: bp::map_indexing_suite<
172+
Container, NoProxy,
173+
emplace_set_derived_policies<Container, NoProxy> > {
174+
typedef typename Container::key_type index_type;
175+
typedef typename Container::value_type::second_type data_type;
176+
typedef typename Container::value_type value_type;
177+
using DerivedPolicies =
178+
bp::detail::final_map_derived_policies<Container, NoProxy>;
179+
180+
template <class Class>
181+
static void extension_def(Class& cl) {
182+
// Wrap the map's element (value_type)
183+
std::string elem_name = "map_indexing_suite_";
184+
bp::object class_name(cl.attr("__name__"));
185+
bp::extract<std::string> class_name_extractor(class_name);
186+
elem_name += class_name_extractor();
187+
elem_name += "_entry";
188+
namespace mpl = boost::mpl;
189+
190+
typedef typename mpl::if_<
191+
mpl::and_<boost::is_class<data_type>, mpl::bool_<!NoProxy> >,
192+
bp::return_internal_reference<>, bp::default_call_policies>::type
193+
get_data_return_policy;
194+
195+
bp::class_<value_type>(elem_name.c_str(), bp::no_init)
196+
.def("__repr__", &DerivedPolicies::print_elem)
197+
.def("data", &DerivedPolicies::get_data, get_data_return_policy())
198+
.def("key", &DerivedPolicies::get_key);
199+
}
200+
201+
static void set_item(Container& container, index_type i, data_type const& v) {
202+
container.emplace(i, v);
203+
}
204+
};
205+
206+
/**
207+
* @brief Expose the map-like container, e.g. (std::map).
208+
*
209+
* @param[in] Container Container to expose.
210+
* @param[in] NoProxy When set to false, the elements will be copied when
211+
* returned to Python.
212+
*/
213+
template <class Container, bool NoProxy = false>
214+
struct GenericMapVisitor
215+
: public emplace_set_derived_policies<Container, NoProxy>,
216+
public dict_to_map<Container> {
217+
typedef dict_to_map<Container> FromPythonDictConverter;
218+
219+
template <typename DerivedVisitor>
220+
static void expose(const std::string& class_name,
221+
const std::string& doc_string,
222+
const bp::def_visitor<DerivedVisitor>& visitor) {
223+
namespace bp = bp;
224+
225+
if (!register_symbolic_link_to_registered_type<Container>()) {
226+
bp::class_<Container>(class_name.c_str(), doc_string.c_str())
227+
.def(GenericMapVisitor())
228+
.def("todict", &FromPythonDictConverter::todict, bp::arg("self"),
229+
"Returns the map type as a Python dictionary.")
230+
.def_pickle(PickleMap<Container>())
231+
.def(visitor);
232+
// Register conversion
233+
FromPythonDictConverter::register_converter();
234+
}
235+
}
236+
237+
static void expose(const std::string& class_name,
238+
const std::string& doc_string = "") {
239+
expose(class_name, doc_string, EmptyPythonVisitor());
240+
}
241+
242+
template <typename DerivedVisitor>
243+
static void expose(const std::string& class_name,
244+
const bp::def_visitor<DerivedVisitor>& visitor) {
245+
expose(class_name, "", visitor);
246+
}
247+
};
248+
249+
} // namespace eigenpy
250+
251+
#endif // ifndef __eigenpy_map_hpp__

0 commit comments

Comments
 (0)