I have a C++ class with Python bindings using pybind11.
Now I want to mark the binding of one method as deprecated. Let's assume it looks something like this:
PYBIND11_MODULE(my_module, m)
{
pybind11::class_<Foobar>(m, "PyFoobar")
.def("old_foo", &Foobar::foo) // <-- this is deprecated in favour of "new_foo"
.def("new_foo", &Foobar::foo);
}
What is the best way to mark PyFoobar.old_foo()
as deprecated such that the user notices it when calling the method? Ideally I would like a DeprecationWarning
to be triggered.
Okay, here is my working example. I actually couldn't figure out to call the imported python function with a Python C Type, so I just went straight to the C API, which should perform better anyway
struct Foobar {
Foobar() {}
void foo(int32_t art) {}
};
PYBIND11_MODULE(example, m) {
pybind11::class_<Foobar>(m, "PyFoobar")
.def(py::init<>())
.def("old_foo",
[](pybind11::object &self, int arg_to_foo)
{
PyErr_WarnEx(PyExc_DeprecationWarning,
"old_foo() is deprecated, use new_foo() instead.",
1);
return self.attr("new_foo")(arg_to_foo);
})
.def("new_foo", &Foobar::foo);
}
You can pass any of the warning types from here as the first argument: https://docs.python.org/3/c-api/exceptions.html#standard-warning-categories
The final int is the level of the stack that you want to be flagged as being deprecated.
So looking at this python code
import example
import warnings
warnings.simplefilter("default")
def mary():
f = example.PyFoobar()
f.old_foo(1)
mary()
If you set the stack level to 1
you'll get
test.py:9: DeprecationWarning: old_foo() is deprecated, use new_foo() instead.
f.old_foo(1)
You might want 2
which will give you the actual calling context, but depends on what your use case is
test.py:11: DeprecationWarning: old_foo() is deprecated, use new_foo() instead.
mary()
It is also important to note that by default, many versions of python have Deprecation warnings turned off. You can check this by checking the value of
warnings.filters
. That is why in my example I have the call to warnings.simplefilter("default")
which enables all types of warnings, but only the first time they are hit. There are other ways you can control this as well, including using the -W
flag when running python or an environment variable.
https://docs.python.org/3/library/warnings.html#describing-warning-filters
I found some way to get most of what I want: Use a lambda for the deprecated binding. In this lambda, issue the warning and then call the actual function. As in my example, the only change is the name, I simply call new_foo
inside old_foo
. If the actual function that is bound differs, this would get more complicated.
PYBIND11_MODULE(my_module, m)
{
pybind11::class_<Foobar>(m, "PyFoobar")
.def("old_foo",
[](pybind11::object &self, int arg_to_foo)
{
auto warnings = pybind11::module::import("warnings");
warnings.attr("warn")(
"old_foo() is deprecated, use new_foo() instead.");
return self.attr("new_foo")(arg_to_foo);
})
.def("new_foo", &Foobar::foo);
}
This results in
UserWarning: old_foo() is deprecated, use new_foo() instead.
When old_foo()
is called for the first time.
Unfortunately, I did not yet figure out how to make it a DeprecationWarning
instead of UserWarning
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With