From b8c5dbdef5671e467d43e14de7a888c197996b87 Mon Sep 17 00:00:00 2001 From: Dean Moldovan Date: Thu, 24 Aug 2017 02:46:07 +0200 Subject: [PATCH] Show a deprecation warning for old-style `__init__` and `__setstate__` The warning is shown at module initialization time (on import, not when the functions are called). It's only visible when compiled in debug mode. --- docs/upgrade.rst | 126 +++++++++++++++++++----------------- include/pybind11/pybind11.h | 14 ++++ 2 files changed, 80 insertions(+), 60 deletions(-) diff --git a/docs/upgrade.rst b/docs/upgrade.rst index bcbc6b135..f993e2047 100644 --- a/docs/upgrade.rst +++ b/docs/upgrade.rst @@ -37,6 +37,72 @@ The old macro emits a compile-time deprecation warning. } +New API for defining custom constructors and pickling functions +--------------------------------------------------------------- + +The old placement-new custom constructors have been deprecated. The new approach +uses ``py::init()`` and factory functions to greatly improve type safety. + +Placement-new can be called accidentally with an incompatible type (without any +compiler errors or warnings), or it can initialize the same object multiple times +if not careful with the Python-side ``__init__`` calls. The new-style custom +constructors prevent such mistakes. See :ref:`custom_constructors` for details. + +.. code-block:: cpp + + // old -- deprecated (runtime warning shown only in debug mode) + py::class(m, "Foo") + .def("__init__", [](Foo &self, ...) { + new (&self) Foo(...); // uses placement-new + }); + + // new + py::class(m, "Foo") + .def(py::init([](...) { // Note: no `self` argument + return new Foo(...); // return by raw pointer + // or: return std::make_unique(...); // return by holder + // or: return Foo(...); // return by value (move constructor) + })); + +Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred +way to get and set object state. See :ref:`pickling` for details. + +.. code-block:: cpp + + // old -- deprecated (runtime warning shown only in debug mode) + py::class(m, "Foo") + ... + .def("__getstate__", [](const Foo &self) { + return py::make_tuple(self.value1(), self.value2(), ...); + }) + .def("__setstate__", [](Foo &self, py::tuple t) { + new (&self) Foo(t[0].cast(), ...); + }); + + // new + py::class(m, "Foo") + ... + .def(py::pickle( + [](const Foo &self) { // __getstate__ + return py::make_tuple(f.value1(), f.value2(), ...); // unchanged + }, + [](py::tuple t) { // __setstate__, note: no `self` argument + return new Foo(t[0].cast(), ...); + // or: return std::make_unique(...); // return by holder + // or: return Foo(...); // return by value (move constructor) + } + )); + +For both the constructors and pickling, warnings are shown at module +initialization time (on import, not when the functions are called). +They're only visible when compiled in debug mode. Sample warning: + +.. code-block:: none + + pybind11-bound class 'mymodule.Foo' is using an old-style placement-new '__init__' + which has been deprecated. See the upgrade guide in pybind11's docs. + + Stricter enforcement of hidden symbol visibility for pybind11 modules --------------------------------------------------------------------- @@ -135,66 +201,6 @@ localize all common type bindings in order to avoid conflicts with third-party modules. -New syntax for custom constructors ----------------------------------- - -The old placement-new custom constructors are still valid, but the new approach -greatly improves type safety. Placement-new can be called accidentally with an -incompatible type (without any compiler errors or warnings), or it can initialize -the same object multiple times if not careful with the Python-side ``__init__`` -calls. The new-style ``py::init()`` custom constructors prevent such mistakes. -See :ref:`custom_constructors` for details. - -.. code-block:: cpp - - // old - py::class(m, "Foo") - .def("__init__", [](Foo &self, ...) { - new (&self) Foo(...); // uses placement-new - }); - - // new - py::class(m, "Foo") - .def(py::init([](...) { // Note: no `self` argument - return new Foo(...); // return by raw pointer - // or: return std::make_unique(...); // return by holder - // or: return Foo(...); // return by value (move constructor) - })); - - -New syntax for pickling support -------------------------------- - -Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred -way to get and set object state. See :ref:`pickling` for details. - -.. code-block:: cpp - - // old -- deprecated - py::class(m, "Foo") - ... - .def("__getstate__", [](const Foo &self) { - return py::make_tuple(self.value1(), self.value2(), ...); - }) - .def("__setstate__", [](Foo &self, py::tuple t) { - new (&self) Foo(t[0].cast(), ...); - }); - - // new - py::class(m, "Foo") - ... - .def(py::pickle( - [](const Foo &self) { // __getstate__ - return py::make_tuple(f.value1(), f.value2(), ...); // unchanged - }, - [](py::tuple t) { // __setstate__, note: no `self` argument - return new Foo(t[0].cast(), ...); - // or: return std::make_unique(...); // return by holder - // or: return Foo(...); // return by value (move constructor) - } - )); - - Deprecation of some ``py::object`` APIs --------------------------------------- diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 0e67c4060..b6e5fa699 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -201,6 +201,20 @@ protected: rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__"); +#if !defined(NDEBUG) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING) + if (rec->is_constructor && !rec->is_new_style_constructor) { + const auto class_name = std::string(((PyTypeObject *) rec->scope.ptr())->tp_name); + const auto func_name = std::string(rec->name); + PyErr_WarnEx( + PyExc_FutureWarning, + ("pybind11-bound class '" + class_name + "' is using an old-style " + "placement-new '" + func_name + "' which has been deprecated. See " + "the upgrade guide in pybind11's docs. This message is only visible " + "when compiled in debug mode.").c_str(), 0 + ); + } +#endif + /* Generate a proper function signature */ std::string signature; size_t type_depth = 0, char_index = 0, type_index = 0, arg_index = 0;