Adding bind_map

This commit is contained in:
Sergey Lyskov 2016-08-29 22:50:38 -04:00
parent ba21f1dbd6
commit 7520418e26
4 changed files with 193 additions and 10 deletions

View File

@ -551,6 +551,18 @@ and the Python ``list``, ``set`` and ``dict`` data structures are automatically
enabled. The types ``std::pair<>`` and ``std::tuple<>`` are already supported
out of the box with just the core :file:`pybind11/pybind11.h` header.
Alternatively it might be desirable to bind STL containers as native C++ classes,
eliminating the need of converting back and forth between C++ representation
and Python one. The downside of this approach in this case users will have to
deal with C++ containers directly instead of using already familiar Python lists
or dicts.
Pybind11 provide set of binder functions to bind various STL containers like vectors,
maps etc. All binder functions are designed to return instances of pybind11::class_
objects so developers can bind extra functions if needed. For complete set of
available functions please see :file:`pybind11/stl_bind.h`. For an example on using
this feature, please see :file:`tests/test_stl_binders.cpp`.
.. note::
Arbitrary nesting of any of these types is supported.

View File

@ -12,6 +12,7 @@
#include "common.h"
#include "operators.h"
#include <map>
#include <type_traits>
#include <utility>
#include <algorithm>
@ -130,10 +131,12 @@ template <typename Vector, typename Class_> auto vector_if_insertion_operator(Cl
NAMESPACE_END(detail)
template <typename T, typename Allocator = std::allocator<T>, typename holder_type = std::unique_ptr<std::vector<T, Allocator>>, typename... Args>
pybind11::class_<std::vector<T, Allocator>, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
using Vector = std::vector<T, Allocator>;
//
// std::vector
//
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
pybind11::class_<Vector, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) {
using T = typename Vector::value_type;
using SizeType = typename Vector::size_type;
using DiffType = typename Vector::difference_type;
using ItType = typename Vector::iterator;
@ -350,4 +353,118 @@ pybind11::class_<std::vector<T, Allocator>, holder_type> bind_vector(pybind11::m
return cl;
}
//
// std::map
//
NAMESPACE_BEGIN(detail)
/* Fallback functions */
template <typename, typename, typename... Args> void map_if_insertion_operator(const Args&...) { }
template <typename Map, typename Class_, typename... Args> void map_if_copy_assignable(Class_ &cl, const Args&...) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
cl.def("__setitem__",
[](Map &m, const KeyType &k, const MappedType &v) {
auto it = m.find(k);
if (it != m.end()) it->second = v;
else m.emplace(k, v);
});
}
template<typename Map, typename Class_, typename std::enable_if<!std::is_copy_assignable<typename Map::mapped_type>::value, int>::type = 0>
void map_if_copy_assignable(Class_ &cl) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
cl.def("__setitem__",
[](Map &m, const KeyType &k, const MappedType &v) {
auto r = m.insert( std::make_pair(k, v) ); // We can't use m[k] = v; because value type might not be default constructable
if (!r.second) { // value type might be const so the only way to insert it is to errase it first...
m.erase(r.first);
m.insert( std::make_pair(k, v) );
}
});
}
template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &cl, std::string const &name)
-> decltype(std::declval<std::ostream&>() << std::declval<typename Map::key_type>() << std::declval<typename Map::mapped_type>(), void()) {
cl.def("__repr__",
[name](Map &m) {
std::ostringstream s;
s << name << '{';
bool f = false;
for (auto const & kv : m) {
if (f) s << ", ";
s << kv.first << ": " << kv.second;
f = true;
}
s << '}';
return s.str();
},
"Return the canonical string representation of this map."
);
}
NAMESPACE_END(detail)
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
pybind11::class_<Map, holder_type> bind_map(module &m, const std::string &name, Args&&... args) {
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using Class_ = pybind11::class_<Map, holder_type>;
Class_ cl(m, name.c_str(), std::forward<Args>(args)...);
cl.def(pybind11::init<>());
// Register stream insertion operator (if possible)
detail::map_if_insertion_operator<Map, Class_>(cl, name);
cl.def("__bool__",
[](const Map &m) -> bool {
return !m.empty();
},
"Check whether the map is nonempty"
);
cl.def("__iter__",
[](Map &m) {
return pybind11::make_key_iterator(m.begin(), m.end());
},
pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
);
cl.def("items",
[](Map &m) { return pybind11::make_iterator(m.begin(), m.end()); },
pybind11::keep_alive<0, 1>() /* Essential: keep list alive while iterator exists */
);
cl.def("__getitem__",
[](Map &m, const KeyType &k) -> MappedType {
auto it = m.find(k);
if (it != m.end()) return it->second;
else throw pybind11::key_error(); // it is not always possible to convert key to string // pybind11::key_error(k)
});
detail::map_if_copy_assignable<Map, Class_>(cl);
cl.def("__delitem__",
[](Map &m, const KeyType &k) {
auto it = m.find(k);
if (it != m.end()) return m.erase(it);
else throw pybind11::key_error(); // it is not always possible to convert key to string // pybind11::key_error(k)
});
cl.def("__len__", &Map::size);
return cl;
}
NAMESPACE_END(pybind11)

View File

@ -28,10 +28,18 @@ test_initializer stl_binder_vector([](py::module &m) {
py::class_<El>(m, "El")
.def(py::init<int>());
py::bind_vector<unsigned int>(m, "VectorInt");
py::bind_vector<bool>(m, "VectorBool");
py::bind_vector< std::vector<unsigned int> >(m, "VectorInt");
py::bind_vector< std::vector<bool> >(m, "VectorBool");
py::bind_vector<El>(m, "VectorEl");
py::bind_vector< std::vector<El> >(m, "VectorEl");
py::bind_vector<std::vector<El>>(m, "VectorVectorEl");
py::bind_vector< std::vector< std::vector<El> > >(m, "VectorVectorEl");
});
test_initializer stl_binder_map([](py::module &m) {
py::bind_map< std::map<std::string, double> >(m, "MapStringDouble");
py::bind_map< std::unordered_map<std::string, double> >(m, "UnorderedMapStringDouble");
py::bind_map< std::map<std::string, double const> >(m, "MapStringDoubleConst");
py::bind_map< std::unordered_map<std::string, double const> >(m, "UnorderedMapStringDoubleConst");
});

View File

@ -1,5 +1,3 @@
def test_vector_int():
from pybind11_tests import VectorInt
@ -51,3 +49,51 @@ def test_vector_bool():
for i in range(10):
assert vv_c[i] == (i % 2 == 0)
assert str(vv_c) == "VectorBool[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]"
def test_map_string_double():
from pybind11_tests import MapStringDouble, UnorderedMapStringDouble
m = MapStringDouble()
m['a'] = 1
m['b'] = 2.5
keys = []
for k in m: keys.append(k)
assert keys == ['a', 'b']
key_values = []
for k, v in m.items(): key_values.append( (k, v) )
assert key_values == [('a', 1), ('b', 2.5) ]
assert str(m) == "MapStringDouble{a: 1, b: 2.5}"
um = UnorderedMapStringDouble()
um['ua'] = 1.1
um['ub'] = 2.6
keys = []
for k in um: keys.append(k)
assert sorted(keys) == ['ua', 'ub']
key_values = []
for k, v in um.items(): key_values.append( (k, v) )
assert sorted(key_values) == [('ua', 1.1), ('ub', 2.6) ]
str(um)
def test_map_string_double_const():
from pybind11_tests import MapStringDoubleConst, UnorderedMapStringDoubleConst
mc = MapStringDoubleConst()
mc['a'] = 10
mc['b'] = 20.5
assert str(mc) == "MapStringDoubleConst{a: 10, b: 20.5}"
umc = UnorderedMapStringDoubleConst()
umc['a'] = 11
umc['b'] = 21.5
str(umc)