diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index c889dc4169..68db345ea0 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1578,6 +1578,18 @@ class class_ : public detail::generic_type { 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 6ba1f5f201..b96b3967c1 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 @@ class staticmethod : public object { 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 c8b8071dc9..768cd072a8 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 ff9196f0f2..e06f92bfe9 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: