From df5b5d37aaefd5190857de51f4999ef3e9e05bc8 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Mon, 28 Dec 2020 14:40:50 +0100 Subject: [PATCH 1/3] Ignore old-style __init__ warnings --- tests/pybind11_tests.h | 14 +++++ tests/test_factory_constructors.cpp | 79 +++++++++++++------------ tests/test_pickling.cpp | 92 +++++++++++++++-------------- 3 files changed, 104 insertions(+), 81 deletions(-) diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 4ff56c07de..7facee8dfc 100644 --- a/tests/pybind11_tests.h +++ b/tests/pybind11_tests.h @@ -69,3 +69,17 @@ template<> class type_caster { }; PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(pybind11) + +/// Simplified ``with warnigns.catch_warnings()`` wrapper +template +void ignoreOldStyleInitWarnings(F &&body) { + auto message = "pybind11-bound class '.+' is using an old-style placement-new '(?:__init__|__setstate__)' which has been deprecated"; + auto category = py::reinterpret_borrow(PyExc_FutureWarning); + auto warnings = py::module_::import("warnings"); + auto context_mgr = warnings.attr("catch_warnings")(); + context_mgr.attr("__enter__")(); + warnings.attr("filterwarnings")("ignore", py::arg("message")=message, py::arg("category")=category); + body(); + // Exceptions in `body` not handled; see PEP 343 when these would need to be added + context_mgr.attr("__exit__")(py::none(), py::none(), py::none()); +} diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index f42d1f29b9..30ab4b59d9 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -183,25 +183,27 @@ TEST_SUBMODULE(factory_constructors, m) { auto c4a = [c](pointer_tag, TF4_tag, int a) { (void) c; return new TestFactory4(a);}; // test_init_factory_basic, test_init_factory_casting - py::class_>(m, "TestFactory3") - .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); })) - .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); })) - .def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }) // placement-new ctor - - // factories returning a derived type: - .def(py::init(c4a)) // derived ptr - .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); })) - // derived shared ptr: - .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared(a); })) - .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared(a); })) - - // Returns nullptr: - .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; })) - .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr(); })) - .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr(); })) - - .def_readwrite("value", &TestFactory3::value) - ; + ignoreOldStyleInitWarnings([&]() { + py::class_>(m, "TestFactory3") + .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); })) + .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); })) + .def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }) // placement-new ctor + + // factories returning a derived type: + .def(py::init(c4a)) // derived ptr + .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); })) + // derived shared ptr: + .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared(a); })) + .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared(a); })) + + // Returns nullptr: + .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; })) + .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr(); })) + .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr(); })) + + .def_readwrite("value", &TestFactory3::value) + ; + }); // test_init_factory_casting py::class_>(m, "TestFactory4") @@ -304,24 +306,27 @@ TEST_SUBMODULE(factory_constructors, m) { static void operator delete(void *p) { py::print("noisy delete"); ::operator delete(p); } #endif }; - py::class_(m, "NoisyAlloc") - // Since these overloads have the same number of arguments, the dispatcher will try each of - // them until the arguments convert. Thus we can get a pre-allocation here when passing a - // single non-integer: - .def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }) // Regular constructor, runs first, requires preallocation - .def(py::init([](double d) { return new NoisyAlloc(d); })) - - // The two-argument version: first the factory pointer overload. - .def(py::init([](int i, int) { return new NoisyAlloc(i); })) - // Return-by-value: - .def(py::init([](double d, int) { return NoisyAlloc(d); })) - // Old-style placement new init; requires preallocation - .def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); }) - // Requires deallocation of previous overload preallocated value: - .def(py::init([](int i, double) { return new NoisyAlloc(i); })) - // Regular again: requires yet another preallocation - .def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); }) - ; + + ignoreOldStyleInitWarnings([&]() { + py::class_(m, "NoisyAlloc") + // Since these overloads have the same number of arguments, the dispatcher will try each of + // them until the arguments convert. Thus we can get a pre-allocation here when passing a + // single non-integer: + .def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }) // Regular constructor, runs first, requires preallocation + .def(py::init([](double d) { return new NoisyAlloc(d); })) + + // The two-argument version: first the factory pointer overload. + .def(py::init([](int i, int) { return new NoisyAlloc(i); })) + // Return-by-value: + .def(py::init([](double d, int) { return NoisyAlloc(d); })) + // Old-style placement new init; requires preallocation + .def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); }) + // Requires deallocation of previous overload preallocated value: + .def(py::init([](int i, double) { return new NoisyAlloc(i); })) + // Regular again: requires yet another preallocation + .def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); }) + ; + }); diff --git a/tests/test_pickling.cpp b/tests/test_pickling.cpp index 9dc63bda3b..53f816cf35 100644 --- a/tests/test_pickling.cpp +++ b/tests/test_pickling.cpp @@ -31,29 +31,31 @@ TEST_SUBMODULE(pickling, m) { using Pickleable::Pickleable; }; - py::class_(m, "Pickleable") - .def(py::init()) - .def("value", &Pickleable::value) - .def("extra1", &Pickleable::extra1) - .def("extra2", &Pickleable::extra2) - .def("setExtra1", &Pickleable::setExtra1) - .def("setExtra2", &Pickleable::setExtra2) - // For details on the methods below, refer to - // http://docs.python.org/3/library/pickle.html#pickling-class-instances - .def("__getstate__", [](const Pickleable &p) { - /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(p.value(), p.extra1(), p.extra2()); - }) - .def("__setstate__", [](Pickleable &p, py::tuple t) { - if (t.size() != 3) - throw std::runtime_error("Invalid state!"); - /* Invoke the constructor (need to use in-place version) */ - new (&p) Pickleable(t[0].cast()); - - /* Assign any additional state */ - p.setExtra1(t[1].cast()); - p.setExtra2(t[2].cast()); - }); + ignoreOldStyleInitWarnings([&]() { + py::class_(m, "Pickleable") + .def(py::init()) + .def("value", &Pickleable::value) + .def("extra1", &Pickleable::extra1) + .def("extra2", &Pickleable::extra2) + .def("setExtra1", &Pickleable::setExtra1) + .def("setExtra2", &Pickleable::setExtra2) + // For details on the methods below, refer to + // http://docs.python.org/3/library/pickle.html#pickling-class-instances + .def("__getstate__", [](const Pickleable &p) { + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p.value(), p.extra1(), p.extra2()); + }) + .def("__setstate__", [](Pickleable &p, py::tuple t) { + if (t.size() != 3) + throw std::runtime_error("Invalid state!"); + /* Invoke the constructor (need to use in-place version) */ + new (&p) Pickleable(t[0].cast()); + + /* Assign any additional state */ + p.setExtra1(t[1].cast()); + p.setExtra2(t[2].cast()); + }); + }); py::class_(m, "PickleableNew") .def(py::init()) @@ -87,27 +89,29 @@ TEST_SUBMODULE(pickling, m) { using PickleableWithDict::PickleableWithDict; }; - py::class_(m, "PickleableWithDict", py::dynamic_attr()) - .def(py::init()) - .def_readwrite("value", &PickleableWithDict::value) - .def_readwrite("extra", &PickleableWithDict::extra) - .def("__getstate__", [](py::object self) { - /* Also include __dict__ in state */ - return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__")); - }) - .def("__setstate__", [](py::object self, py::tuple t) { - if (t.size() != 3) - throw std::runtime_error("Invalid state!"); - /* Cast and construct */ - auto& p = self.cast(); - new (&p) PickleableWithDict(t[0].cast()); - - /* Assign C++ state */ - p.extra = t[1].cast(); - - /* Assign Python state */ - self.attr("__dict__") = t[2]; - }); + ignoreOldStyleInitWarnings([&]() { + py::class_(m, "PickleableWithDict", py::dynamic_attr()) + .def(py::init()) + .def_readwrite("value", &PickleableWithDict::value) + .def_readwrite("extra", &PickleableWithDict::extra) + .def("__getstate__", [](py::object self) { + /* Also include __dict__ in state */ + return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__")); + }) + .def("__setstate__", [](py::object self, py::tuple t) { + if (t.size() != 3) + throw std::runtime_error("Invalid state!"); + /* Cast and construct */ + auto& p = self.cast(); + new (&p) PickleableWithDict(t[0].cast()); + + /* Assign C++ state */ + p.extra = t[1].cast(); + + /* Assign Python state */ + self.attr("__dict__") = t[2]; + }); + }); py::class_(m, "PickleableWithDictNew") .def(py::init()) From d445d3f419f9894bef3497041dd569bd54a9ae64 Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 29 Dec 2020 00:12:34 +0100 Subject: [PATCH 2/3] Simplify ignoreOldStyleInitWarnings with py::exec --- tests/pybind11_tests.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/pybind11_tests.h b/tests/pybind11_tests.h index 7facee8dfc..ccb0529787 100644 --- a/tests/pybind11_tests.h +++ b/tests/pybind11_tests.h @@ -1,5 +1,6 @@ #pragma once #include +#include #if defined(_MSC_VER) && _MSC_VER < 1910 // We get some really long type names here which causes MSVC 2015 to emit warnings @@ -70,16 +71,14 @@ template<> class type_caster { PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_END(pybind11) -/// Simplified ``with warnigns.catch_warnings()`` wrapper template void ignoreOldStyleInitWarnings(F &&body) { - auto message = "pybind11-bound class '.+' is using an old-style placement-new '(?:__init__|__setstate__)' which has been deprecated"; - auto category = py::reinterpret_borrow(PyExc_FutureWarning); - auto warnings = py::module_::import("warnings"); - auto context_mgr = warnings.attr("catch_warnings")(); - context_mgr.attr("__enter__")(); - warnings.attr("filterwarnings")("ignore", py::arg("message")=message, py::arg("category")=category); - body(); - // Exceptions in `body` not handled; see PEP 343 when these would need to be added - context_mgr.attr("__exit__")(py::none(), py::none(), py::none()); + py::exec(R"( + message = "pybind11-bound class '.+' is using an old-style placement-new '(?:__init__|__setstate__)' which has been deprecated" + + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message=message, category=FutureWarning) + body() + )", py::dict(py::arg("body") = py::cpp_function(body))); } From a483f2d21e86b007cfd626301312ef90e3f4da9a Mon Sep 17 00:00:00 2001 From: Yannick Jadoul Date: Tue, 29 Dec 2020 14:34:12 +0100 Subject: [PATCH 3/3] Only wrap single class_::defs to ignore DeprecationWarnings about old-style __init__ --- tests/test_factory_constructors.cpp | 84 +++++++++++++++-------------- tests/test_pickling.cpp | 50 +++++++++-------- 2 files changed, 72 insertions(+), 62 deletions(-) diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp index 30ab4b59d9..7ff7e7b52c 100644 --- a/tests/test_factory_constructors.cpp +++ b/tests/test_factory_constructors.cpp @@ -183,27 +183,28 @@ TEST_SUBMODULE(factory_constructors, m) { auto c4a = [c](pointer_tag, TF4_tag, int a) { (void) c; return new TestFactory4(a);}; // test_init_factory_basic, test_init_factory_casting - ignoreOldStyleInitWarnings([&]() { - py::class_>(m, "TestFactory3") - .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); })) - .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); })) - .def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }) // placement-new ctor - - // factories returning a derived type: - .def(py::init(c4a)) // derived ptr - .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); })) - // derived shared ptr: - .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared(a); })) - .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared(a); })) - - // Returns nullptr: - .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; })) - .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr(); })) - .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr(); })) - - .def_readwrite("value", &TestFactory3::value) - ; + py::class_> pyTestFactory3(m, "TestFactory3"); + pyTestFactory3 + .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); })) + .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); })); + ignoreOldStyleInitWarnings([&pyTestFactory3]() { + pyTestFactory3.def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }); // placement-new ctor }); + pyTestFactory3 + // factories returning a derived type: + .def(py::init(c4a)) // derived ptr + .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); })) + // derived shared ptr: + .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared(a); })) + .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared(a); })) + + // Returns nullptr: + .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; })) + .def(py::init([](null_unique_ptr_tag) { return std::unique_ptr(); })) + .def(py::init([](null_shared_ptr_tag) { return std::shared_ptr(); })) + + .def_readwrite("value", &TestFactory3::value) + ; // test_init_factory_casting py::class_>(m, "TestFactory4") @@ -307,25 +308,30 @@ TEST_SUBMODULE(factory_constructors, m) { #endif }; - ignoreOldStyleInitWarnings([&]() { - py::class_(m, "NoisyAlloc") - // Since these overloads have the same number of arguments, the dispatcher will try each of - // them until the arguments convert. Thus we can get a pre-allocation here when passing a - // single non-integer: - .def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }) // Regular constructor, runs first, requires preallocation - .def(py::init([](double d) { return new NoisyAlloc(d); })) - - // The two-argument version: first the factory pointer overload. - .def(py::init([](int i, int) { return new NoisyAlloc(i); })) - // Return-by-value: - .def(py::init([](double d, int) { return NoisyAlloc(d); })) - // Old-style placement new init; requires preallocation - .def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); }) - // Requires deallocation of previous overload preallocated value: - .def(py::init([](int i, double) { return new NoisyAlloc(i); })) - // Regular again: requires yet another preallocation - .def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); }) - ; + + py::class_ pyNoisyAlloc(m, "NoisyAlloc"); + // Since these overloads have the same number of arguments, the dispatcher will try each of + // them until the arguments convert. Thus we can get a pre-allocation here when passing a + // single non-integer: + ignoreOldStyleInitWarnings([&pyNoisyAlloc]() { + pyNoisyAlloc.def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }); // Regular constructor, runs first, requires preallocation + }); + + pyNoisyAlloc.def(py::init([](double d) { return new NoisyAlloc(d); })); + + // The two-argument version: first the factory pointer overload. + pyNoisyAlloc.def(py::init([](int i, int) { return new NoisyAlloc(i); })); + // Return-by-value: + pyNoisyAlloc.def(py::init([](double d, int) { return NoisyAlloc(d); })); + // Old-style placement new init; requires preallocation + ignoreOldStyleInitWarnings([&pyNoisyAlloc]() { + pyNoisyAlloc.def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); }); + }); + // Requires deallocation of previous overload preallocated value: + pyNoisyAlloc.def(py::init([](int i, double) { return new NoisyAlloc(i); })); + // Regular again: requires yet another preallocation + ignoreOldStyleInitWarnings([&pyNoisyAlloc]() { + pyNoisyAlloc.def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); }); }); diff --git a/tests/test_pickling.cpp b/tests/test_pickling.cpp index 53f816cf35..1a48595af6 100644 --- a/tests/test_pickling.cpp +++ b/tests/test_pickling.cpp @@ -31,20 +31,22 @@ TEST_SUBMODULE(pickling, m) { using Pickleable::Pickleable; }; - ignoreOldStyleInitWarnings([&]() { - py::class_(m, "Pickleable") - .def(py::init()) - .def("value", &Pickleable::value) - .def("extra1", &Pickleable::extra1) - .def("extra2", &Pickleable::extra2) - .def("setExtra1", &Pickleable::setExtra1) - .def("setExtra2", &Pickleable::setExtra2) - // For details on the methods below, refer to - // http://docs.python.org/3/library/pickle.html#pickling-class-instances - .def("__getstate__", [](const Pickleable &p) { - /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(p.value(), p.extra1(), p.extra2()); - }) + py::class_ pyPickleable(m, "Pickleable"); + pyPickleable + .def(py::init()) + .def("value", &Pickleable::value) + .def("extra1", &Pickleable::extra1) + .def("extra2", &Pickleable::extra2) + .def("setExtra1", &Pickleable::setExtra1) + .def("setExtra2", &Pickleable::setExtra2) + // For details on the methods below, refer to + // http://docs.python.org/3/library/pickle.html#pickling-class-instances + .def("__getstate__", [](const Pickleable &p) { + /* Return a tuple that fully encodes the state of the object */ + return py::make_tuple(p.value(), p.extra1(), p.extra2()); + }); + ignoreOldStyleInitWarnings([&pyPickleable]() { + pyPickleable .def("__setstate__", [](Pickleable &p, py::tuple t) { if (t.size() != 3) throw std::runtime_error("Invalid state!"); @@ -89,15 +91,17 @@ TEST_SUBMODULE(pickling, m) { using PickleableWithDict::PickleableWithDict; }; - ignoreOldStyleInitWarnings([&]() { - py::class_(m, "PickleableWithDict", py::dynamic_attr()) - .def(py::init()) - .def_readwrite("value", &PickleableWithDict::value) - .def_readwrite("extra", &PickleableWithDict::extra) - .def("__getstate__", [](py::object self) { - /* Also include __dict__ in state */ - return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__")); - }) + py::class_ pyPickleableWithDict(m, "PickleableWithDict", py::dynamic_attr()); + pyPickleableWithDict + .def(py::init()) + .def_readwrite("value", &PickleableWithDict::value) + .def_readwrite("extra", &PickleableWithDict::extra) + .def("__getstate__", [](py::object self) { + /* Also include __dict__ in state */ + return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__")); + }); + ignoreOldStyleInitWarnings([&pyPickleableWithDict]() { + pyPickleableWithDict .def("__setstate__", [](py::object self, py::tuple t) { if (t.size() != 3) throw std::runtime_error("Invalid state!");