Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -1470,27 +1470,30 @@ class iterator : public object {
PYBIND11_OBJECT_DEFAULT(iterator, object, PyIter_Check)

iterator &operator++() {
init();
advance();
return *this;
}

iterator operator++(int) {
// Note: We must call init() first so that rv.value is
// the same as this->value just before calling advance().
// Otherwise, dereferencing the returned iterator may call
// advance() again and return the 3rd item instead of the 1st.
init();
auto rv = *this;
advance();
return rv;
}

// NOLINTNEXTLINE(readability-const-return-type) // PR #3263
reference operator*() const {
if (m_ptr && !value.ptr()) {
auto &self = const_cast<iterator &>(*this);
self.advance();
}
init();
return value;
}

pointer operator->() const {
operator*();
init();
return &value;
}

Expand All @@ -1513,6 +1516,13 @@ class iterator : public object {
friend bool operator!=(const iterator &a, const iterator &b) { return a->ptr() != b->ptr(); }

private:
void init() const {
if (m_ptr && !value.ptr()) {
auto &self = const_cast<iterator &>(*this);
self.advance();
}
}

void advance() {
value = reinterpret_steal<object>(PyIter_Next(m_ptr));
if (value.ptr() == nullptr && PyErr_Occurred()) {
Expand Down
12 changes: 12 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ TEST_SUBMODULE(pytypes, m) {
m.def("get_iterator", [] { return py::iterator(); });
// test_iterable
m.def("get_iterable", [] { return py::iterable(); });
m.def("get_first_item_from_iterable", [](const py::iterable &iter) {
// This tests the postfix increment operator
py::iterator it = iter.begin();
py::iterator it2 = it++;
return *it2;
});
m.def("get_second_item_from_iterable", [](const py::iterable &iter) {
// This tests the prefix increment operator
py::iterator it = iter.begin();
++it;
return *it;
});
m.def("get_frozenset_from_iterable",
[](const py::iterable &iter) { return py::frozenset(iter); });
m.def("get_list_from_iterable", [](const py::iterable &iter) { return py::list(iter); });
Expand Down
5 changes: 5 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def test_from_iterable(pytype, from_iter_func):

def test_iterable(doc):
assert doc(m.get_iterable) == "get_iterable() -> Iterable"
lins = [1, 2, 3]
i = m.get_first_item_from_iterable(lins)
assert i == 1
i = m.get_second_item_from_iterable(lins)
assert i == 2


def test_float(doc):
Expand Down