Skip to content

Commit ed466bc

Browse files
committed
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.
1 parent e6a94cb commit ed466bc

File tree

2 files changed

+80
-60
lines changed

2 files changed

+80
-60
lines changed

docs/upgrade.rst

Lines changed: 66 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,72 @@ The old macro emits a compile-time deprecation warning.
3737
}
3838
3939
40+
New API for defining custom constructors and pickling functions
41+
---------------------------------------------------------------
42+
43+
The old placement-new custom constructors have been deprecated. The new approach
44+
uses ``py::init()`` and factory functions to greatly improve type safety.
45+
46+
Placement-new can be called accidentally with an incompatible type (without any
47+
compiler errors or warnings), or it can initialize the same object multiple times
48+
if not careful with the Python-side ``__init__`` calls. The new-style custom
49+
constructors prevent such mistakes. See :ref:`custom_constructors` for details.
50+
51+
.. code-block:: cpp
52+
53+
// old -- deprecated (runtime warning shown only in debug mode)
54+
py::class<Foo>(m, "Foo")
55+
.def("__init__", [](Foo &self, ...) {
56+
new (&self) Foo(...); // uses placement-new
57+
});
58+
59+
// new
60+
py::class<Foo>(m, "Foo")
61+
.def(py::init([](...) { // Note: no `self` argument
62+
return new Foo(...); // return by raw pointer
63+
// or: return std::make_unique<Foo>(...); // return by holder
64+
// or: return Foo(...); // return by value (move constructor)
65+
}));
66+
67+
Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred
68+
way to get and set object state. See :ref:`pickling` for details.
69+
70+
.. code-block:: cpp
71+
72+
// old -- deprecated (runtime warning shown only in debug mode)
73+
py::class<Foo>(m, "Foo")
74+
...
75+
.def("__getstate__", [](const Foo &self) {
76+
return py::make_tuple(self.value1(), self.value2(), ...);
77+
})
78+
.def("__setstate__", [](Foo &self, py::tuple t) {
79+
new (&self) Foo(t[0].cast<std::string>(), ...);
80+
});
81+
82+
// new
83+
py::class<Foo>(m, "Foo")
84+
...
85+
.def(py::pickle(
86+
[](const Foo &self) { // __getstate__
87+
return py::make_tuple(f.value1(), f.value2(), ...); // unchanged
88+
},
89+
[](py::tuple t) { // __setstate__, note: no `self` argument
90+
return new Foo(t[0].cast<std::string>(), ...);
91+
// or: return std::make_unique<Foo>(...); // return by holder
92+
// or: return Foo(...); // return by value (move constructor)
93+
}
94+
));
95+
96+
For both the constructors and pickling, warnings are shown at module
97+
initialization time (on import, not when the functions are called).
98+
They're only visible when compiled in debug mode. Sample warning:
99+
100+
.. code-block:: none
101+
102+
pybind11-bound class 'mymodule.Foo' is using an old-style placement-new '__init__'
103+
which has been deprecated. See the upgrade guide in pybind11's docs.
104+
105+
40106
Stricter enforcement of hidden symbol visibility for pybind11 modules
41107
---------------------------------------------------------------------
42108

@@ -135,66 +201,6 @@ localize all common type bindings in order to avoid conflicts with
135201
third-party modules.
136202

137203

138-
New syntax for custom constructors
139-
----------------------------------
140-
141-
The old placement-new custom constructors are still valid, but the new approach
142-
greatly improves type safety. Placement-new can be called accidentally with an
143-
incompatible type (without any compiler errors or warnings), or it can initialize
144-
the same object multiple times if not careful with the Python-side ``__init__``
145-
calls. The new-style ``py::init()`` custom constructors prevent such mistakes.
146-
See :ref:`custom_constructors` for details.
147-
148-
.. code-block:: cpp
149-
150-
// old
151-
py::class<Foo>(m, "Foo")
152-
.def("__init__", [](Foo &self, ...) {
153-
new (&self) Foo(...); // uses placement-new
154-
});
155-
156-
// new
157-
py::class<Foo>(m, "Foo")
158-
.def(py::init([](...) { // Note: no `self` argument
159-
return new Foo(...); // return by raw pointer
160-
// or: return std::make_unique<Foo>(...); // return by holder
161-
// or: return Foo(...); // return by value (move constructor)
162-
}));
163-
164-
165-
New syntax for pickling support
166-
-------------------------------
167-
168-
Mirroring the custom constructor changes, ``py::pickle()`` is now the preferred
169-
way to get and set object state. See :ref:`pickling` for details.
170-
171-
.. code-block:: cpp
172-
173-
// old -- deprecated
174-
py::class<Foo>(m, "Foo")
175-
...
176-
.def("__getstate__", [](const Foo &self) {
177-
return py::make_tuple(self.value1(), self.value2(), ...);
178-
})
179-
.def("__setstate__", [](Foo &self, py::tuple t) {
180-
new (&self) Foo(t[0].cast<std::string>(), ...);
181-
});
182-
183-
// new
184-
py::class<Foo>(m, "Foo")
185-
...
186-
.def(py::pickle(
187-
[](const Foo &self) { // __getstate__
188-
return py::make_tuple(f.value1(), f.value2(), ...); // unchanged
189-
},
190-
[](py::tuple t) { // __setstate__, note: no `self` argument
191-
return new Foo(t[0].cast<std::string>(), ...);
192-
// or: return std::make_unique<Foo>(...); // return by holder
193-
// or: return Foo(...); // return by value (move constructor)
194-
}
195-
));
196-
197-
198204
Deprecation of some ``py::object`` APIs
199205
---------------------------------------
200206

include/pybind11/pybind11.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,20 @@ class cpp_function : public function {
201201

202202
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
203203

204+
#if !defined(NDEBUG) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING)
205+
if (rec->is_constructor && !rec->is_new_style_constructor) {
206+
const auto class_name = std::string(((PyTypeObject *) rec->scope.ptr())->tp_name);
207+
const auto func_name = std::string(rec->name);
208+
PyErr_WarnEx(
209+
PyExc_FutureWarning,
210+
("pybind11-bound class '" + class_name + "' is using an old-style "
211+
"placement-new '" + func_name + "' which has been deprecated. See "
212+
"the upgrade guide in pybind11's docs. This message is only visible "
213+
"when compiled in debug mode.").c_str(), 0
214+
);
215+
}
216+
#endif
217+
204218
/* Generate a proper function signature */
205219
std::string signature;
206220
size_t type_depth = 0, char_index = 0, type_index = 0, arg_index = 0;

0 commit comments

Comments
 (0)