diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index bcdf641f4..51efa8b84 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1651,6 +1651,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 = 0> class_ &def(const T &op, const Extra &...extra) { op.execute(*this, extra...); diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 1e76d7bc1..748368521 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -1357,6 +1357,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: @@ -2269,6 +2270,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 9001d86b1..2ab4c34b3 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -82,7 +82,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_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 seq_id"); py::class_(m, "NoConstructorNew") .def(py::init([]() { return nullptr; })) // Need a NOOP __init__ diff --git a/tests/test_class.py b/tests/test_class.py index 9b2b1d834..45d8ffae8 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -41,6 +41,13 @@ def test_instance_new(): assert cstats.alive() == 0 +def test_classmethod(num_instances=10): + assert not hasattr(m.NoConstructor, "seq_id") + for i in range(num_instances): + m.NoConstructor.new_instance_seq_id() + assert m.NoConstructor.seq_id == i + 1 + + def test_type(): assert m.check_type(1) == m.DerivedClass1 with pytest.raises(RuntimeError) as execinfo: