diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 88de78d024..2aa6ffcb53 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -2371,7 +2371,12 @@ args_proxy object_api::operator*() const { template template bool object_api::contains(T &&item) const { - return attr("__contains__")(std::forward(item)).template cast(); + const int result + = PySequence_Contains(derived().ptr(), object_or_cast(std::forward(item)).ptr()); + if (result == -1) { + throw error_already_set(); + } + return result != 0; } template diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b500eb7972..d9a1203dca 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -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(); }); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 3c7b31e093..91e5e6f106 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -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)