Skip to content

Commit 0b4be8d

Browse files
committed
[CPyCppyy] Don't re-implement contains() in Python if already present
The newer C++ standards already implement a `contains()` method for multiple STL classes. We can just forward to these in the `__contains__` method for the Python proxy. Forwarding to `contains()` on the C++ side means also that it will become easier to implement classes that Pythonize nicely automatically. Just as happened already before with `size()` (that got used to automatically implement `__len__`), one just needs to implement `contains()` on the C++ side now to implicitly get the `__contains__` pythonization.
1 parent cffea8e commit 0b4be8d

File tree

4 files changed

+28
-1
lines changed

4 files changed

+28
-1
lines changed

bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
PyObject* CPyCppyy::PyStrings::gAssign = nullptr;
88
PyObject* CPyCppyy::PyStrings::gBases = nullptr;
99
PyObject* CPyCppyy::PyStrings::gBase = nullptr;
10+
PyObject* CPyCppyy::PyStrings::gContains = nullptr;
1011
PyObject* CPyCppyy::PyStrings::gCopy = nullptr;
1112
PyObject* CPyCppyy::PyStrings::gCppBool = nullptr;
1213
PyObject* CPyCppyy::PyStrings::gCppName = nullptr;
@@ -88,6 +89,7 @@ bool CPyCppyy::CreatePyStrings() {
8889
CPPYY_INITIALIZE_STRING(gAssign, __assign__);
8990
CPPYY_INITIALIZE_STRING(gBases, __bases__);
9091
CPPYY_INITIALIZE_STRING(gBase, __base__);
92+
CPPYY_INITIALIZE_STRING(gContains, contains);
9193
CPPYY_INITIALIZE_STRING(gCopy, copy);
9294
#if PY_VERSION_HEX < 0x03000000
9395
CPPYY_INITIALIZE_STRING(gCppBool, __cpp_nonzero__);
@@ -171,6 +173,7 @@ PyObject* CPyCppyy::DestroyPyStrings() {
171173
// Remove all cached python strings.
172174
Py_DECREF(PyStrings::gBases); PyStrings::gBases = nullptr;
173175
Py_DECREF(PyStrings::gBase); PyStrings::gBase = nullptr;
176+
Py_DECREF(PyStrings::gContains); PyStrings::gContains = nullptr;
174177
Py_DECREF(PyStrings::gCopy); PyStrings::gCopy = nullptr;
175178
Py_DECREF(PyStrings::gCppBool); PyStrings::gCppBool = nullptr;
176179
Py_DECREF(PyStrings::gCppName); PyStrings::gCppName = nullptr;

bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ namespace PyStrings {
1010
extern PyObject* gAssign;
1111
extern PyObject* gBases;
1212
extern PyObject* gBase;
13+
extern PyObject* gContains;
1314
extern PyObject* gCopy;
1415
extern PyObject* gCppBool;
1516
extern PyObject* gCppName;

bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ PyObject* MapInit(PyObject* self, PyObject* args, PyObject* /* kwds */)
898898
return nullptr;
899899
}
900900

901+
#if __cplusplus <= 202002L
901902
PyObject* STLContainsWithFind(PyObject* self, PyObject* obj)
902903
{
903904
// Implement python's __contains__ for std::map/std::set
@@ -924,6 +925,7 @@ PyObject* STLContainsWithFind(PyObject* self, PyObject* obj)
924925

925926
return result;
926927
}
928+
#endif
927929

928930

929931
//- set behavior as primitives ------------------------------------------------
@@ -1266,6 +1268,7 @@ PyObject* STLStringDecode(CPPInstance* self, PyObject* args, PyObject* kwds)
12661268
return PyUnicode_Decode(obj->data(), obj->size(), encoding, errors);
12671269
}
12681270

1271+
#if __cplusplus <= 202302L
12691272
PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj)
12701273
{
12711274
std::string* obj = GetSTLString(self);
@@ -1282,6 +1285,7 @@ PyObject* STLStringContains(CPPInstance* self, PyObject* pyobj)
12821285

12831286
Py_RETURN_FALSE;
12841287
}
1288+
#endif
12851289

12861290
PyObject* STLStringReplace(CPPInstance* self, PyObject* args, PyObject* /*kwds*/)
12871291
{
@@ -1654,6 +1658,10 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
16541658
Utility::AddToClass(pyclass, "__len__", "size");
16551659
}
16561660

1661+
if (HasAttrDirect(pyclass, PyStrings::gContains, /*mustBeCPyCppyy=*/ true)) {
1662+
Utility::AddToClass(pyclass, "__contains__", "contains");
1663+
}
1664+
16571665
if (!IsTemplatedSTLClass(name, "vector") && // vector is dealt with below
16581666
!((PyTypeObject*)pyclass)->tp_iter) {
16591667
if (HasAttrDirect(pyclass, PyStrings::gBegin) && HasAttrDirect(pyclass, PyStrings::gEnd)) {
@@ -1885,16 +1893,21 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
18851893
// constructor that takes python associative collections
18861894
Utility::AddToClass(pyclass, "__real_init", "__init__");
18871895
Utility::AddToClass(pyclass, "__init__", (PyCFunction)MapInit, METH_VARARGS | METH_KEYWORDS);
1888-
1896+
#if __cplusplus <= 202002L
1897+
// From C++20, std::map and std::unordered_map already implement a contains() method.
18891898
Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O);
1899+
#endif
18901900
}
18911901

18921902
else if (IsTemplatedSTLClass(name, "set")) {
18931903
// constructor that takes python associative collections
18941904
Utility::AddToClass(pyclass, "__real_init", "__init__");
18951905
Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, METH_VARARGS | METH_KEYWORDS);
18961906

1907+
#if __cplusplus <= 202002L
1908+
// From C++20, std::set already implements a contains() method.
18971909
Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O);
1910+
#endif
18981911
}
18991912

19001913
else if (IsTemplatedSTLClass(name, "pair")) {
@@ -1922,7 +1935,10 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name)
19221935
Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLStringCompare, METH_O);
19231936
Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, METH_O);
19241937
Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, METH_O);
1938+
#if __cplusplus <= 202302L
1939+
// From C++23, std::sting already implements a contains() method.
19251940
Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, METH_O);
1941+
#endif
19261942
Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, METH_VARARGS | METH_KEYWORDS);
19271943
Utility::AddToClass(pyclass, "__cpp_find", "find");
19281944
Utility::AddToClass(pyclass, "find", (PyCFunction)STLStringFind, METH_VARARGS | METH_KEYWORDS);

bindings/pyroot/cppyy/cppyy/test/test_stltypes.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1868,6 +1868,13 @@ def test05_contains(self):
18681868
for i in range(100):
18691869
assert not (2**30 in S)
18701870

1871+
assert '__contains__' in cppyy.gbl.std.string.__dict__
1872+
my_string = cppyy.gbl.std.string("hello world")
1873+
1874+
assert "hello" in my_string
1875+
assert "world" in my_string
1876+
assert "bye" not in my_string
1877+
18711878

18721879
class TestSTLTUPLE:
18731880
def setup_class(cls):

0 commit comments

Comments
 (0)