Skip to content
Open
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
8 changes: 7 additions & 1 deletion include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -2032,7 +2032,13 @@ class sequence : public object {
return object::operator[](std::forward<T>(o));
}
detail::sequence_iterator begin() const { return {*this, 0}; }
detail::sequence_iterator end() const { return {*this, PySequence_Size(m_ptr)}; }
detail::sequence_iterator end() const {
ssize_t result = PySequence_Size(m_ptr);
if (result == -1) {
throw error_already_set();
}
return {*this, result};
}
};

class list : public object {
Expand Down
14 changes: 14 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,20 @@ TEST_SUBMODULE(pytypes, m) {
return py::tuple();
});

m.def("capi_sequence_end", [](const py::sequence &seq) {
py::detail::sequence_iterator end;
try {
end = seq.end();
} catch (const py::error_already_set &) {
throw;
}
if (PyErr_Occurred()) {
py::raise_from(PyExc_AssertionError, "sequence::end returned with a pending error");
throw py::error_already_set();
}
return end - seq.begin();
});

m.def("accessor_assignment", []() {
auto l = py::list(1);
l[0] = 0;
Expand Down
43 changes: 43 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,49 @@ def test_cpp_iterators():
assert m.passed_iterator(iter((-7, 3))) == -4


@pytest.mark.parametrize("error_type", [RuntimeError, KeyboardInterrupt, SystemExit])
def test_capi_sequence_end_error(error_type):
error = error_type("sequence length failed")

class BadLength:
def __getitem__(self, index):
raise AssertionError("end must not retrieve items")

def __len__(self):
raise error

with pytest.raises(error_type) as excinfo:
m.capi_sequence_end(BadLength())
assert excinfo.value is error
assert "__len__" in [entry.name for entry in excinfo.traceback]


def test_capi_sequence_end_negative_length():
class NegativeLength:
def __getitem__(self, index):
raise AssertionError("end must not retrieve items")

def __len__(self):
return -1 # noqa: PLE0303 - deliberately invalid length

with pytest.raises(ValueError):
m.capi_sequence_end(NegativeLength())


@pytest.mark.parametrize("values", [[], [4, 7]])
@pytest.mark.parametrize("kind", [list, tuple, "custom"])
def test_capi_sequence_end_valid(values, kind):
class CustomSequence:
def __getitem__(self, index):
return values[index]

def __len__(self):
return len(values)

sequence = CustomSequence() if kind == "custom" else kind(values)
assert m.capi_sequence_end(sequence) == len(values)


def test_implementation_details():
lst = [39, 43, 92, 49, 22, 29, 93, 98, 26, 57, 8]
tup = tuple(lst)
Expand Down
Loading