Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 1 | /* |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 2 | tests/test_sequences_and_iterators.cpp -- supporting Pythons' sequence protocol, iterators, |
Wenzel Jakob | a576e6a | 2015-07-29 17:51:54 +0200 | [diff] [blame] | 3 | etc. |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 4 | |
Wenzel Jakob | 8cb6cb3 | 2016-04-17 20:21:41 +0200 | [diff] [blame] | 5 | Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch> |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 6 | |
| 7 | All rights reserved. Use of this source code is governed by a |
| 8 | BSD-style license that can be found in the LICENSE file. |
| 9 | */ |
| 10 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 11 | #include "pybind11_tests.h" |
| 12 | #include "constructor_stats.h" |
Wenzel Jakob | 8f4eb00 | 2015-10-15 18:13:33 +0200 | [diff] [blame] | 13 | #include <pybind11/operators.h> |
| 14 | #include <pybind11/stl.h> |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 15 | |
| 16 | class Sequence { |
| 17 | public: |
| 18 | Sequence(size_t size) : m_size(size) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 19 | print_created(this, "of size", m_size); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 20 | m_data = new float[size]; |
| 21 | memset(m_data, 0, sizeof(float) * size); |
| 22 | } |
| 23 | |
| 24 | Sequence(const std::vector<float> &value) : m_size(value.size()) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 25 | print_created(this, "of size", m_size, "from std::vector"); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 26 | m_data = new float[m_size]; |
| 27 | memcpy(m_data, &value[0], sizeof(float) * m_size); |
| 28 | } |
| 29 | |
| 30 | Sequence(const Sequence &s) : m_size(s.m_size) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 31 | print_copy_created(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 32 | m_data = new float[m_size]; |
| 33 | memcpy(m_data, s.m_data, sizeof(float)*m_size); |
| 34 | } |
| 35 | |
| 36 | Sequence(Sequence &&s) : m_size(s.m_size), m_data(s.m_data) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 37 | print_move_created(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 38 | s.m_size = 0; |
| 39 | s.m_data = nullptr; |
| 40 | } |
| 41 | |
| 42 | ~Sequence() { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 43 | print_destroyed(this); |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 44 | delete[] m_data; |
| 45 | } |
| 46 | |
| 47 | Sequence &operator=(const Sequence &s) { |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 48 | if (&s != this) { |
| 49 | delete[] m_data; |
| 50 | m_size = s.m_size; |
| 51 | m_data = new float[m_size]; |
| 52 | memcpy(m_data, s.m_data, sizeof(float)*m_size); |
| 53 | } |
| 54 | |
| 55 | print_copy_assigned(this); |
| 56 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 57 | return *this; |
| 58 | } |
| 59 | |
| 60 | Sequence &operator=(Sequence &&s) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 61 | if (&s != this) { |
| 62 | delete[] m_data; |
| 63 | m_size = s.m_size; |
| 64 | m_data = s.m_data; |
| 65 | s.m_size = 0; |
| 66 | s.m_data = nullptr; |
| 67 | } |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 68 | |
| 69 | print_move_assigned(this); |
| 70 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 71 | return *this; |
| 72 | } |
| 73 | |
| 74 | bool operator==(const Sequence &s) const { |
| 75 | if (m_size != s.size()) |
| 76 | return false; |
| 77 | for (size_t i=0; i<m_size; ++i) |
| 78 | if (m_data[i] != s[i]) |
| 79 | return false; |
| 80 | return true; |
| 81 | } |
| 82 | |
| 83 | bool operator!=(const Sequence &s) const { |
| 84 | return !operator==(s); |
| 85 | } |
| 86 | |
| 87 | float operator[](size_t index) const { |
| 88 | return m_data[index]; |
| 89 | } |
| 90 | |
| 91 | float &operator[](size_t index) { |
| 92 | return m_data[index]; |
| 93 | } |
| 94 | |
| 95 | bool contains(float v) const { |
| 96 | for (size_t i=0; i<m_size; ++i) |
| 97 | if (v == m_data[i]) |
| 98 | return true; |
| 99 | return false; |
| 100 | } |
| 101 | |
| 102 | Sequence reversed() const { |
| 103 | Sequence result(m_size); |
| 104 | for (size_t i=0; i<m_size; ++i) |
| 105 | result[m_size-i-1] = m_data[i]; |
| 106 | return result; |
| 107 | } |
| 108 | |
| 109 | size_t size() const { return m_size; } |
| 110 | |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 111 | const float *begin() const { return m_data; } |
| 112 | const float *end() const { return m_data+m_size; } |
| 113 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 114 | private: |
| 115 | size_t m_size; |
| 116 | float *m_data; |
| 117 | }; |
| 118 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame] | 119 | class IntPairs { |
| 120 | public: |
| 121 | IntPairs(std::vector<std::pair<int, int>> data) : data_(std::move(data)) {} |
| 122 | const std::pair<int, int>* begin() const { return data_.data(); } |
| 123 | |
| 124 | private: |
| 125 | std::vector<std::pair<int, int>> data_; |
| 126 | }; |
| 127 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 128 | // Interface of a map-like object that isn't (directly) an unordered_map, but provides some basic |
| 129 | // map-like functionality. |
| 130 | class StringMap { |
| 131 | public: |
Jason Rhinelander | 0780655 | 2016-11-12 20:41:31 -0500 | [diff] [blame] | 132 | StringMap() = default; |
| 133 | StringMap(std::unordered_map<std::string, std::string> init) |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 134 | : map(std::move(init)) {} |
| 135 | |
| 136 | void set(std::string key, std::string val) { |
| 137 | map[key] = val; |
| 138 | } |
| 139 | |
| 140 | std::string get(std::string key) const { |
| 141 | return map.at(key); |
| 142 | } |
| 143 | |
| 144 | size_t size() const { |
| 145 | return map.size(); |
| 146 | } |
| 147 | |
| 148 | private: |
| 149 | std::unordered_map<std::string, std::string> map; |
| 150 | |
| 151 | public: |
| 152 | decltype(map.cbegin()) begin() const { return map.cbegin(); } |
| 153 | decltype(map.cend()) end() const { return map.cend(); } |
| 154 | }; |
| 155 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame] | 156 | template<typename T> |
| 157 | class NonZeroIterator { |
| 158 | const T* ptr_; |
| 159 | public: |
| 160 | NonZeroIterator(const T* ptr) : ptr_(ptr) {} |
| 161 | const T& operator*() const { return *ptr_; } |
| 162 | NonZeroIterator& operator++() { ++ptr_; return *this; } |
| 163 | }; |
| 164 | |
| 165 | class NonZeroSentinel {}; |
| 166 | |
| 167 | template<typename A, typename B> |
| 168 | bool operator==(const NonZeroIterator<std::pair<A, B>>& it, const NonZeroSentinel&) { |
| 169 | return !(*it).first || !(*it).second; |
| 170 | } |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 171 | |
Dean Moldovan | 5637af7 | 2017-02-09 23:05:33 +0100 | [diff] [blame] | 172 | template <typename PythonType> |
| 173 | py::list test_random_access_iterator(PythonType x) { |
| 174 | if (x.size() < 5) |
| 175 | throw py::value_error("Please provide at least 5 elements for testing."); |
| 176 | |
| 177 | auto checks = py::list(); |
| 178 | auto assert_equal = [&checks](py::handle a, py::handle b) { |
| 179 | auto result = PyObject_RichCompareBool(a.ptr(), b.ptr(), Py_EQ); |
| 180 | if (result == -1) { throw py::error_already_set(); } |
| 181 | checks.append(result != 0); |
| 182 | }; |
| 183 | |
| 184 | auto it = x.begin(); |
| 185 | assert_equal(x[0], *it); |
| 186 | assert_equal(x[0], it[0]); |
| 187 | assert_equal(x[1], it[1]); |
| 188 | |
| 189 | assert_equal(x[1], *(++it)); |
| 190 | assert_equal(x[1], *(it++)); |
| 191 | assert_equal(x[2], *it); |
| 192 | assert_equal(x[3], *(it += 1)); |
| 193 | assert_equal(x[2], *(--it)); |
| 194 | assert_equal(x[2], *(it--)); |
| 195 | assert_equal(x[1], *it); |
| 196 | assert_equal(x[0], *(it -= 1)); |
| 197 | |
| 198 | assert_equal(it->attr("real"), x[0].attr("real")); |
| 199 | assert_equal((it + 1)->attr("real"), x[1].attr("real")); |
| 200 | |
| 201 | assert_equal(x[1], *(it + 1)); |
| 202 | assert_equal(x[1], *(1 + it)); |
| 203 | it += 3; |
| 204 | assert_equal(x[1], *(it - 2)); |
| 205 | |
| 206 | checks.append(static_cast<std::size_t>(x.end() - x.begin()) == x.size()); |
| 207 | checks.append((x.begin() + static_cast<std::ptrdiff_t>(x.size())) == x.end()); |
| 208 | checks.append(x.begin() < x.end()); |
| 209 | |
| 210 | return checks; |
| 211 | } |
| 212 | |
Dean Moldovan | f768582 | 2017-02-08 14:31:49 +0100 | [diff] [blame] | 213 | test_initializer sequences_and_iterators([](py::module &pm) { |
| 214 | auto m = pm.def_submodule("sequences_and_iterators"); |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame] | 215 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 216 | py::class_<Sequence> seq(m, "Sequence"); |
| 217 | |
| 218 | seq.def(py::init<size_t>()) |
| 219 | .def(py::init<const std::vector<float>&>()) |
| 220 | /// Bare bones interface |
| 221 | .def("__getitem__", [](const Sequence &s, size_t i) { |
| 222 | if (i >= s.size()) |
| 223 | throw py::index_error(); |
| 224 | return s[i]; |
| 225 | }) |
| 226 | .def("__setitem__", [](Sequence &s, size_t i, float v) { |
| 227 | if (i >= s.size()) |
| 228 | throw py::index_error(); |
| 229 | s[i] = v; |
| 230 | }) |
| 231 | .def("__len__", &Sequence::size) |
| 232 | /// Optional sequence protocol operations |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 233 | .def("__iter__", [](const Sequence &s) { return py::make_iterator(s.begin(), s.end()); }, |
| 234 | py::keep_alive<0, 1>() /* Essential: keep object alive while iterator exists */) |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 235 | .def("__contains__", [](const Sequence &s, float v) { return s.contains(v); }) |
| 236 | .def("__reversed__", [](const Sequence &s) -> Sequence { return s.reversed(); }) |
| 237 | /// Slicing protocol (optional) |
| 238 | .def("__getitem__", [](const Sequence &s, py::slice slice) -> Sequence* { |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 239 | size_t start, stop, step, slicelength; |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 240 | if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) |
| 241 | throw py::error_already_set(); |
| 242 | Sequence *seq = new Sequence(slicelength); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 243 | for (size_t i=0; i<slicelength; ++i) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 244 | (*seq)[i] = s[start]; start += step; |
| 245 | } |
| 246 | return seq; |
| 247 | }) |
| 248 | .def("__setitem__", [](Sequence &s, py::slice slice, const Sequence &value) { |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 249 | size_t start, stop, step, slicelength; |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 250 | if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) |
| 251 | throw py::error_already_set(); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 252 | if (slicelength != value.size()) |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 253 | throw std::runtime_error("Left and right hand size of slice assignment have different sizes!"); |
Wenzel Jakob | 0a07805 | 2016-05-29 13:40:40 +0200 | [diff] [blame] | 254 | for (size_t i=0; i<slicelength; ++i) { |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 255 | s[start] = value[i]; start += step; |
| 256 | } |
| 257 | }) |
| 258 | /// Comparisons |
| 259 | .def(py::self == py::self) |
| 260 | .def(py::self != py::self); |
| 261 | // Could also define py::self + py::self for concatenation, etc. |
| 262 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 263 | py::class_<StringMap> map(m, "StringMap"); |
| 264 | |
| 265 | map .def(py::init<>()) |
| 266 | .def(py::init<std::unordered_map<std::string, std::string>>()) |
| 267 | .def("__getitem__", [](const StringMap &map, std::string key) { |
| 268 | try { return map.get(key); } |
| 269 | catch (const std::out_of_range&) { |
| 270 | throw py::key_error("key '" + key + "' does not exist"); |
| 271 | } |
| 272 | }) |
| 273 | .def("__setitem__", &StringMap::set) |
| 274 | .def("__len__", &StringMap::size) |
| 275 | .def("__iter__", [](const StringMap &map) { return py::make_key_iterator(map.begin(), map.end()); }, |
| 276 | py::keep_alive<0, 1>()) |
| 277 | .def("items", [](const StringMap &map) { return py::make_iterator(map.begin(), map.end()); }, |
| 278 | py::keep_alive<0, 1>()) |
| 279 | ; |
| 280 | |
Ivan Smirnov | 4c5e21b | 2016-08-24 23:30:00 +0100 | [diff] [blame] | 281 | py::class_<IntPairs>(m, "IntPairs") |
| 282 | .def(py::init<std::vector<std::pair<int, int>>>()) |
| 283 | .def("nonzero", [](const IntPairs& s) { |
| 284 | return py::make_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()), NonZeroSentinel()); |
| 285 | }, py::keep_alive<0, 1>()) |
| 286 | .def("nonzero_keys", [](const IntPairs& s) { |
| 287 | return py::make_key_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()), NonZeroSentinel()); |
| 288 | }, py::keep_alive<0, 1>()); |
| 289 | |
Jason Rhinelander | 5aa85be | 2016-08-11 21:22:05 -0400 | [diff] [blame] | 290 | |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 291 | #if 0 |
| 292 | // Obsolete: special data structure for exposing custom iterator types to python |
| 293 | // kept here for illustrative purposes because there might be some use cases which |
| 294 | // are not covered by the much simpler py::make_iterator |
| 295 | |
| 296 | struct PySequenceIterator { |
| 297 | PySequenceIterator(const Sequence &seq, py::object ref) : seq(seq), ref(ref) { } |
| 298 | |
| 299 | float next() { |
| 300 | if (index == seq.size()) |
| 301 | throw py::stop_iteration(); |
| 302 | return seq[index++]; |
| 303 | } |
| 304 | |
| 305 | const Sequence &seq; |
| 306 | py::object ref; // keep a reference |
| 307 | size_t index = 0; |
| 308 | }; |
| 309 | |
Wenzel Jakob | 38bd711 | 2015-07-05 20:05:44 +0200 | [diff] [blame] | 310 | py::class_<PySequenceIterator>(seq, "Iterator") |
| 311 | .def("__iter__", [](PySequenceIterator &it) -> PySequenceIterator& { return it; }) |
| 312 | .def("__next__", &PySequenceIterator::next); |
Wenzel Jakob | b282595 | 2016-04-13 23:33:00 +0200 | [diff] [blame] | 313 | |
| 314 | On the actual Sequence object, the iterator would be constructed as follows: |
| 315 | .def("__iter__", [](py::object s) { return PySequenceIterator(s.cast<const Sequence &>(), s); }) |
| 316 | #endif |
Dean Moldovan | f768582 | 2017-02-08 14:31:49 +0100 | [diff] [blame] | 317 | |
| 318 | m.def("object_to_list", [](py::object o) { |
| 319 | auto l = py::list(); |
| 320 | for (auto item : o) { |
| 321 | l.append(item); |
| 322 | } |
| 323 | return l; |
| 324 | }); |
| 325 | |
| 326 | m.def("iterator_to_list", [](py::iterator it) { |
| 327 | auto l = py::list(); |
| 328 | while (it != py::iterator::sentinel()) { |
| 329 | l.append(*it); |
| 330 | ++it; |
| 331 | } |
| 332 | return l; |
| 333 | }); |
Dean Moldovan | 1fac1b9 | 2017-02-09 12:08:14 +0100 | [diff] [blame] | 334 | |
| 335 | // Make sure that py::iterator works with std algorithms |
| 336 | m.def("count_none", [](py::object o) { |
| 337 | return std::count_if(o.begin(), o.end(), [](py::handle h) { return h.is_none(); }); |
| 338 | }); |
| 339 | |
| 340 | m.def("find_none", [](py::object o) { |
| 341 | auto it = std::find_if(o.begin(), o.end(), [](py::handle h) { return h.is_none(); }); |
| 342 | return it->is_none(); |
| 343 | }); |
Dean Moldovan | 5637af7 | 2017-02-09 23:05:33 +0100 | [diff] [blame] | 344 | |
| 345 | m.def("count_nonzeros", [](py::dict d) { |
| 346 | return std::count_if(d.begin(), d.end(), [](std::pair<py::handle, py::handle> p) { |
| 347 | return p.second.cast<int>() != 0; |
| 348 | }); |
| 349 | }); |
| 350 | |
| 351 | m.def("tuple_iterator", [](py::tuple x) { return test_random_access_iterator(x); }); |
| 352 | m.def("list_iterator", [](py::list x) { return test_random_access_iterator(x); }); |
| 353 | m.def("sequence_iterator", [](py::sequence x) { return test_random_access_iterator(x); }); |
Jason Rhinelander | 52f4be8 | 2016-09-03 14:54:22 -0400 | [diff] [blame] | 354 | }); |