From e5f866626ebb40f905464e5a3fd887c310f68249 Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Wed, 24 Aug 2022 10:07:25 -0400 Subject: [PATCH 1/2] Implement classmethod pytype --- include/pybind11/pybind11.h | 12 ++++++++++++ include/pybind11/pytypes.h | 6 ++++++ tests/test_class.cpp | 10 +++++++++- tests/test_class.py | 6 ++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index c889dc416..68db345ea 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1578,6 +1578,18 @@ public: return *this; } + template + class_ &def_classmethod(const char *name_, Func &&f, const Extra &...extra) { + cpp_function cf(std::forward(f), + name(name_), + is_method(*this), + sibling(getattr(*this, name_, none())), + extra...); + auto cf_name = cf.name(); + attr(std::move(cf_name)) = classmethod(std::move(cf)); + return *this; + } + template class_ &def(const detail::op_ &op, const Extra &...extra) { op.execute(*this, extra...); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 6ba1f5f20..b96b3967c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1224,6 +1224,7 @@ inline bool PyUnicode_Check_Permissive(PyObject *o) { #endif inline bool PyStaticMethod_Check(PyObject *o) { return o->ob_type == &PyStaticMethod_Type; } +inline bool PyClassMethod_Check(PyObject *o) { return o->ob_type == &PyClassMethod_Type; } class kwargs_proxy : public handle { public: @@ -2089,6 +2090,11 @@ public: PYBIND11_OBJECT_CVT(staticmethod, object, detail::PyStaticMethod_Check, PyStaticMethod_New) }; +class classmethod : public object { +public: + PYBIND11_OBJECT_CVT(classmethod, object, detail::PyClassMethod_Check, PyClassMethod_New) +}; + class buffer : public object { public: PYBIND11_OBJECT_DEFAULT(buffer, object, PyObject_CheckBuffer) diff --git a/tests/test_class.cpp b/tests/test_class.cpp index c8b8071dc..768cd072a 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -62,7 +62,15 @@ TEST_SUBMODULE(class_, m) { }; py::class_(m, "NoConstructor") - .def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); + .def_static("new_instance", &NoConstructor::new_instance, "Return an instance") + .def_classmethod( + "new_instance_uuid", + [](py::object &cls) { + py::int_ uuid = getattr(cls, "uuid", py::int_(0)); + cls.attr("uuid") = uuid + py::int_(1); + return NoConstructorNew::new_instance(); + }, + "Returns a new instance and then increment the uuid"); py::class_(m, "NoConstructorNew") .def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ diff --git a/tests/test_class.py b/tests/test_class.py index ff9196f0f..e06f92bfe 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -31,6 +31,12 @@ def test_instance_new(msg): assert cstats.alive() == 0 +def test_classmethod(num_instances=10): + for i in range(num_instances): + assert getattr(m.NoConstructor, "uuid", 0) == i + m.NoConstructor.new_instance_uuid() + + def test_type(): assert m.check_type(1) == m.DerivedClass1 with pytest.raises(RuntimeError) as execinfo: From 2e3d29de45843c028d25d2400edbcacf8c3de4fd Mon Sep 17 00:00:00 2001 From: Aaron Gokaslan Date: Tue, 11 Oct 2022 11:30:09 -0700 Subject: [PATCH 2/2] Update classmethod with reviewer comments --- tests/test_class.cpp | 10 +++++----- tests/test_class.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 768cd072a..0ccb2c051 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -64,13 +64,13 @@ TEST_SUBMODULE(class_, m) { py::class_(m, "NoConstructor") .def_static("new_instance", &NoConstructor::new_instance, "Return an instance") .def_classmethod( - "new_instance_uuid", - [](py::object &cls) { - py::int_ uuid = getattr(cls, "uuid", py::int_(0)); - cls.attr("uuid") = uuid + py::int_(1); + "new_instance_seq_id", + [](py::type &cls) { + py::int_ seq_id = getattr(cls, "seq_id", py::int_(0)); + cls.attr("seq_id") = seq_id + py::int_(1); return NoConstructorNew::new_instance(); }, - "Returns a new instance and then increment the uuid"); + "Returns a new instance and then increment the seq_id"); py::class_(m, "NoConstructorNew") .def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__ diff --git a/tests/test_class.py b/tests/test_class.py index e06f92bfe..dd18f66a0 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -32,9 +32,10 @@ def test_instance_new(msg): def test_classmethod(num_instances=10): + assert not hasattr(m.NoConstructor, "seq_id") for i in range(num_instances): - assert getattr(m.NoConstructor, "uuid", 0) == i - m.NoConstructor.new_instance_uuid() + m.NoConstructor.new_instance_seq_id() + assert m.NoConstructor.seq_id == i + 1 def test_type():