From 7520418e2699429f8d7cd9cf9d6d5f1ff7865b80 Mon Sep 17 00:00:00 2001 From: Sergey Lyskov Date: Mon, 29 Aug 2016 22:50:38 -0400 Subject: [PATCH] Adding bind_map --- docs/advanced.rst | 12 ++++ include/pybind11/stl_bind.h | 125 ++++++++++++++++++++++++++++++++++-- tests/test_stl_binders.cpp | 16 +++-- tests/test_stl_binders.py | 50 ++++++++++++++- 4 files changed, 193 insertions(+), 10 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 301860061..979e0bfd2 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -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. diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 9434510e0..2612b5166 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -12,6 +12,7 @@ #include "common.h" #include "operators.h" +#include #include #include #include @@ -130,10 +131,12 @@ template auto vector_if_insertion_operator(Cl NAMESPACE_END(detail) - -template , typename holder_type = std::unique_ptr>, typename... Args> -pybind11::class_, holder_type> bind_vector(pybind11::module &m, std::string const &name, Args&&... args) { - using Vector = std::vector; +// +// std::vector +// +template , typename... Args> +pybind11::class_ 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_, holder_type> bind_vector(pybind11::m return cl; } + + +// +// std::map +// + +NAMESPACE_BEGIN(detail) + +/* Fallback functions */ +template void map_if_insertion_operator(const Args&...) { } + +template 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::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 auto map_if_insertion_operator(Class_ &cl, std::string const &name) +-> decltype(std::declval() << std::declval() << std::declval(), 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... Args> +pybind11::class_ 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_; + + Class_ cl(m, name.c_str(), std::forward(args)...); + + cl.def(pybind11::init<>()); + + // Register stream insertion operator (if possible) + detail::map_if_insertion_operator(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(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) diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp index a6ed6bf6a..dabcaf01e 100644 --- a/tests/test_stl_binders.cpp +++ b/tests/test_stl_binders.cpp @@ -28,10 +28,18 @@ test_initializer stl_binder_vector([](py::module &m) { py::class_(m, "El") .def(py::init()); - py::bind_vector(m, "VectorInt"); - py::bind_vector(m, "VectorBool"); + py::bind_vector< std::vector >(m, "VectorInt"); + py::bind_vector< std::vector >(m, "VectorBool"); - py::bind_vector(m, "VectorEl"); + py::bind_vector< std::vector >(m, "VectorEl"); - py::bind_vector>(m, "VectorVectorEl"); + py::bind_vector< std::vector< std::vector > >(m, "VectorVectorEl"); +}); + +test_initializer stl_binder_map([](py::module &m) { + py::bind_map< std::map >(m, "MapStringDouble"); + py::bind_map< std::unordered_map >(m, "UnorderedMapStringDouble"); + + py::bind_map< std::map >(m, "MapStringDoubleConst"); + py::bind_map< std::unordered_map >(m, "UnorderedMapStringDoubleConst"); }); diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index 2aaaf3aa2..302635715 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -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)