From 5aa85be26e17f89f0e1bf04870f8efa4765e8326 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Thu, 11 Aug 2016 21:22:05 -0400 Subject: [PATCH] Added pybind11::make_key_iterator for map iteration This allows exposing a dict-like interface to python code, allowing iteration over keys via: for k in custommapping: ... while still allowing iteration over pairs, so that you can also implement 'dict.items()' functionality which returns a pair iterator, allowing: for k, v in custommapping.items(): ... example-sequences-and-iterators is updated with a custom class providing both types of iteration. --- docs/advanced.rst | 6 +++ example/example-sequences-and-iterators.cpp | 47 +++++++++++++++++++++ example/example-sequences-and-iterators.py | 20 ++++++++- example/example-sequences-and-iterators.ref | 14 +++++- include/pybind11/cast.h | 1 + include/pybind11/common.h | 1 + include/pybind11/pybind11.h | 28 +++++++++++- 7 files changed, 113 insertions(+), 4 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 6f57d0be6..c68c33aff 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -957,6 +957,12 @@ exceptions: | | indicate wrong value passed | | | in ``container.remove(...)`` | +--------------------------------------+------------------------------+ +| :class:`pybind11::key_error` | ``KeyError`` (used to | +| | indicate out of bounds | +| | accesses in ``__getitem__``, | +| | ``__setitem__`` in dict-like | +| | objects, etc.) | ++--------------------------------------+------------------------------+ | :class:`pybind11::error_already_set` | Indicates that the Python | | | exception flag has already | | | been initialized | diff --git a/example/example-sequences-and-iterators.cpp b/example/example-sequences-and-iterators.cpp index c677f15d7..791a92926 100644 --- a/example/example-sequences-and-iterators.cpp +++ b/example/example-sequences-and-iterators.cpp @@ -116,6 +116,34 @@ private: float *m_data; }; +// Interface of a map-like object that isn't (directly) an unordered_map, but provides some basic +// map-like functionality. +class StringMap { +public: + StringMap(std::unordered_map init = {}) + : map(std::move(init)) {} + + void set(std::string key, std::string val) { + map[key] = val; + } + + std::string get(std::string key) const { + return map.at(key); + } + + size_t size() const { + return map.size(); + } + +private: + std::unordered_map map; + +public: + decltype(map.cbegin()) begin() const { return map.cbegin(); } + decltype(map.cend()) end() const { return map.cend(); } +}; + + void init_ex_sequences_and_iterators(py::module &m) { py::class_ seq(m, "Sequence"); @@ -164,6 +192,25 @@ void init_ex_sequences_and_iterators(py::module &m) { .def(py::self != py::self); // Could also define py::self + py::self for concatenation, etc. + py::class_ map(m, "StringMap"); + + map .def(py::init<>()) + .def(py::init>()) + .def("__getitem__", [](const StringMap &map, std::string key) { + try { return map.get(key); } + catch (const std::out_of_range&) { + throw py::key_error("key '" + key + "' does not exist"); + } + }) + .def("__setitem__", &StringMap::set) + .def("__len__", &StringMap::size) + .def("__iter__", [](const StringMap &map) { return py::make_key_iterator(map.begin(), map.end()); }, + py::keep_alive<0, 1>()) + .def("items", [](const StringMap &map) { return py::make_iterator(map.begin(), map.end()); }, + py::keep_alive<0, 1>()) + ; + + #if 0 // Obsolete: special data structure for exposing custom iterator types to python // kept here for illustrative purposes because there might be some use cases which diff --git a/example/example-sequences-and-iterators.py b/example/example-sequences-and-iterators.py index 69ec84ed9..764a52767 100755 --- a/example/example-sequences-and-iterators.py +++ b/example/example-sequences-and-iterators.py @@ -3,7 +3,7 @@ from __future__ import print_function import sys sys.path.append('.') -from example import Sequence +from example import Sequence, StringMap s = Sequence(5) print("s = " + str(s)) @@ -29,6 +29,24 @@ for i in rev: print(i, end=' ') print('') +m = StringMap({ 'hi': 'bye', 'black': 'white' }) +print(m['hi']) +print(len(m)) +print(m['black']) +try: + print(m['orange']) + print('Error: should have thrown exception') +except KeyError: + pass +m['orange'] = 'banana' +print(m['orange']) + +for k in m: + print("key = %s, value = %s" % (k, m[k])) + +for k,v in m.items(): + print("item: (%s, %s)" % (k,v)) + from example import ConstructorStats cstats = ConstructorStats.get(Sequence) print("Instances not destroyed:", cstats.alive()) diff --git a/example/example-sequences-and-iterators.ref b/example/example-sequences-and-iterators.ref index 909a93abe..d658fba15 100644 --- a/example/example-sequences-and-iterators.ref +++ b/example/example-sequences-and-iterators.ref @@ -13,9 +13,19 @@ rev[0], rev[1], rev[2], rev[3], rev[4] = 0.000000 56.779999 0.000000 0.000000 12 0.0 56.779998779296875 0.0 0.0 12.34000015258789 0.0 56.779998779296875 0.0 0.0 12.34000015258789 True -### Sequence @ 0x153c4b0 created of size 3 from std::vector -### Sequence @ 0x153c4b0 destroyed +### Sequence @ 0x1b4d1f0 created of size 3 from std::vector +### Sequence @ 0x1b4d1f0 destroyed 2.0 56.779998779296875 2.0 0.0 2.0 +bye +2 +white +banana +key = orange, value = banana +key = hi, value = bye +key = black, value = white +item: (orange, banana) +item: (hi, bye) +item: (black, white) Instances not destroyed: 3 ### Sequence @ 0x1535b00 destroyed Instances not destroyed: 2 diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index cd128663e..185c94352 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -55,6 +55,7 @@ PYBIND11_NOINLINE inline internals &get_internals() { if (p) std::rethrow_exception(p); } catch (const error_already_set &) { return; } catch (const index_error &e) { PyErr_SetString(PyExc_IndexError, e.what()); return; + } catch (const key_error &e) { PyErr_SetString(PyExc_KeyError, e.what()); return; } catch (const value_error &e) { PyErr_SetString(PyExc_ValueError, e.what()); return; } catch (const stop_iteration &e) { PyErr_SetString(PyExc_StopIteration, e.what()); return; } catch (const std::bad_alloc &e) { PyErr_SetString(PyExc_MemoryError, e.what()); return; diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 6a0811616..0b2092920 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -314,6 +314,7 @@ NAMESPACE_END(detail) class error_already_set : public std::runtime_error { public: error_already_set() : std::runtime_error(detail::error_string()) {} }; PYBIND11_RUNTIME_EXCEPTION(stop_iteration) PYBIND11_RUNTIME_EXCEPTION(index_error) +PYBIND11_RUNTIME_EXCEPTION(key_error) PYBIND11_RUNTIME_EXCEPTION(value_error) PYBIND11_RUNTIME_EXCEPTION(cast_error) /// Thrown when pybind11::cast or handle::call fail due to a type casting error PYBIND11_RUNTIME_EXCEPTION(reference_cast_error) /// Used internally diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index ebd6f6f59..427b12c3e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1117,7 +1117,7 @@ PYBIND11_NOINLINE inline void keep_alive_impl(int Nurse, int Patient, handle arg keep_alive_impl(nurse, patient); } -template struct iterator_state { +template struct iterator_state { Iterator it, end; bool first; }; @@ -1148,11 +1148,37 @@ iterator make_iterator(Iterator first, Iterator last, Extra &&... extra) { return (iterator) cast(state { first, last, true }); } +template ()->first), + typename... Extra> +iterator make_key_iterator(Iterator first, Iterator last, Extra &&... extra) { + typedef detail::iterator_state state; + + if (!detail::get_type_info(typeid(state))) { + class_(handle(), "") + .def("__iter__", [](state &s) -> state& { return s; }) + .def("__next__", [](state &s) -> KeyType { + if (!s.first) + ++s.it; + else + s.first = false; + if (s.it == s.end) + throw stop_iteration(); + return s.it->first; + }, return_value_policy::reference_internal, std::forward(extra)...); + } + + return (iterator) cast(state { first, last, true }); +} template iterator make_iterator(Type &value, Extra&&... extra) { return make_iterator(std::begin(value), std::end(value), extra...); } +template iterator make_key_iterator(Type &value, Extra&&... extra) { + return make_key_iterator(std::begin(value), std::end(value), extra...); +} + template void implicitly_convertible() { auto implicit_caster = [](PyObject *obj, PyTypeObject *type) -> PyObject * { if (!detail::type_caster().load(obj, false))