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
7 changes: 6 additions & 1 deletion include/pybind11/pytypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -2371,7 +2371,12 @@ args_proxy object_api<D>::operator*() const {
template <typename D>
template <typename T>
bool object_api<D>::contains(T &&item) const {
return attr("__contains__")(std::forward<T>(item)).template cast<bool>();
const int result
= PySequence_Contains(derived().ptr(), object_or_cast(std::forward<T>(item)).ptr());
if (result == -1) {
throw error_already_set();
}
return result != 0;
}

template <typename D>
Expand Down
2 changes: 2 additions & 0 deletions tests/test_pytypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ TEST_SUBMODULE(pytypes, m) {
[](const py::anyset &set, const py::object &key) { return set.contains(key); });
m.def("anyset_contains",
[](const py::anyset &set, const char *key) { return set.contains(key); });
m.def("object_contains",
[](const py::object &obj, const py::object &item) { return obj.contains(item); });
m.def("set_add", [](py::set &set, const py::object &key) { set.add(key); });
m.def("set_clear", [](py::set &set) { set.clear(); });

Expand Down
20 changes: 20 additions & 0 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@ def test_none(doc):
assert doc(m.print_none) == "print_none(arg0: None) -> None"


def test_object_contains():
class OnlyIter:
def __iter__(self):
return iter([1, 2])

class RaisingContains:
def __contains__(self, item):
raise ValueError("contains failed")

assert m.object_contains([1, 2], 1) is True
assert m.object_contains([1, 2], 3) is False
# The `in` operator falls back to iteration when there is no __contains__.
assert m.object_contains(OnlyIter(), 2) is True
assert m.object_contains(OnlyIter(), 3) is False
with pytest.raises(ValueError, match="contains failed"):
m.object_contains(RaisingContains(), 1)
with pytest.raises(TypeError):
m.object_contains(object(), 1)


def test_set(capture, doc):
s = m.get_set()
assert isinstance(s, set)
Expand Down
Loading