-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Currently, enum and class default arguments in functions generate the following docstrings:
enum class Color {Red};
struct ColorContainer {
Color color;
};
PYBIND11_MODULE(foo, m) {
pybind11::enum_<Color> (m, "Color").value("Red", Color::Red);
m.def("a", [](Color) {}, pybind11::arg("a") = Color::Red);
pybind11::class_<ColorContainer> color_container(m, "ColorContainer");
m.def("b", [](ColorContainer) {}, pybind11::arg("a") = ColorContainer {Color::Red});
}>>> help(foo)
[...]
FUNCTIONS
a(...) method of builtins.PyCapsule instance
a(a: foo.Color = <Color.Red: 0>) -> None
b(...) method of builtins.PyCapsule instance
b(a: foo.ColorContainer = <foo.ColorContainer object at 0x7f7234cb1f70>) -> NoneThis is somewhat unhelpful for the automatic generation of stubfiles. Representations with these brackets are not syntactically valid Python, making parsing of these expressions subject to whatever representation pybind is currently sticking with. Importantly, bound enum values could be fully represented. Additionally, I would argue bound classes should not be represented with strings containing pointer values in docstrings since that representation is random and non-reproducible. Bound class values are more or less impossible to represent as literals (unless some special machinery is added to arg and arg_v to handle some exceptions, like inline construction through literals, e.g. as py::arg("a") = py::literal<BoundClassType>(ctor_args...), although I think that is likely to be controversial.
Suggested resolution
I would suggest altering the docstring generation to fully represent bound enum values, replace bound class instances with an ellipsis ..., and leaving the call to __repr__ for any non-bound types, something like this (please forgive any potential no-nos, I'm admittedly not really familiar with the codebase):
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index e2ddda0..ded5020 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -235,8 +235,24 @@ protected:
a.name = strdup(a.name);
if (a.descr)
a.descr = strdup(a.descr);
- else if (a.value)
- a.descr = strdup(repr(a.value).cast<std::string>().c_str());
+ else if (a.value) {
+ auto typehandle = type::handle_of(a.value);
+ if (detail::get_internals().registered_types_py.count(Py_TYPE(a.value.ptr())) > 0) {
+ if (hasattr(typehandle, "__members__")) {
+ // Bound enum type, can be fully represented
+ auto descr = typehandle.attr("__module__").cast<std::string>();
+ descr += "." + typehandle.attr("__qualname__").cast<std::string>();
+ descr += "." + a.value.attr("name").cast<std::string>();
+ a.descr = strdup(descr.c_str());
+ } else {
+ // Use ellipsis instead of repr to keep syntactic validity
+ a.descr = strdup("...");
+ }
+ } else {
+ // Types not bound by pybind are well-represented by repr
+ a.descr = strdup(repr(a.value).cast<std::string>().c_str());
+ }
+ }
}
rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");This causes the following:
enum class Color {Red};
struct ColorContainer {
Color color;
};
PYBIND11_MODULE(foo, m) {
pybind11::enum_<Color> (m, "Color").value("Red", Color::Red);
m.def("a", [](Color) {}, pybind11::arg("a") = Color::Red);
m.def("b", [](int) {}, pybind11::arg("a") = 1);
m.def("c", [](std::string) {}, pybind11::arg("a") = "Hello");
m.def("d", [](std::vector<int>) {}, pybind11::arg("a") = std::vector<int> {{1, 2, 3, 4}});
pybind11::class_<ColorContainer> color_container(m, "ColorContainer");
m.def("e", [](ColorContainer) {}, pybind11::arg("a") = ColorContainer {Color::Red});
}>>> help(foo)
[...]
FUNCTIONS
a(...) method of builtins.PyCapsule instance
a(a: foo.Color = foo.Color.Red) -> None
b(...) method of builtins.PyCapsule instance
b(a: int = 1) -> None
c(...) method of builtins.PyCapsule instance
c(a: str = 'Hello') -> None
d(...) method of builtins.PyCapsule instance
d(a: List[int] = [1, 2, 3, 4]) -> None
e(...) method of builtins.PyCapsule instance
e(a: foo.ColorContainer = ...) -> None