Skip to content

Commit 7117892

Browse files
authored
__qualname__ and nested class naming fixes (#1171)
A few fixes related to how we set `__qualname__` and how we show the type name in function signatures: - `__qualname__` isn't supposed to have the module name at the beginning, but we've been putting it there. This removes it, while keeping the `Nested.Class` name chaining. - print `__module__.__qualname__` rather than `type->tp_name`; the latter doesn't work properly for nested classes, so we would get `module.B` rather than `module.A.B` for a class `B` with parent `A`. This also unifies the Python 3 and PyPy code. Fixes #1166. - This now sets a `__qualname__` attribute on the type (as would happen in Python 3.3+) for Python <3.3, including PyPy. While not particularly important to have in earlier Python versions, it's useful for us to be able to extracted the nested name, which is why `__qualname__` was invented in the first place. - Added tests for the above.
1 parent 0b3f44e commit 7117892

File tree

4 files changed

+78
-23
lines changed

4 files changed

+78
-23
lines changed

include/pybind11/detail/class.h

+26-10
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@
1414
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
1515
NAMESPACE_BEGIN(detail)
1616

17+
#if PY_VERSION_HEX >= 0x03030000
18+
# define PYBIND11_BUILTIN_QUALNAME
19+
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj)
20+
#else
21+
// In pre-3.3 Python, we still set __qualname__ so that we can produce reliable function type
22+
// signatures; in 3.3+ this macro expands to nothing:
23+
# define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) setattr((PyObject *) obj, "__qualname__", nameobj)
24+
#endif
25+
1726
inline PyTypeObject *type_incref(PyTypeObject *type) {
1827
Py_INCREF(type);
1928
return type;
@@ -48,7 +57,7 @@ inline PyTypeObject *make_static_property_type() {
4857
pybind11_fail("make_static_property_type(): error allocating type!");
4958

5059
heap_type->ht_name = name_obj.inc_ref().ptr();
51-
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
60+
#ifdef PYBIND11_BUILTIN_QUALNAME
5261
heap_type->ht_qualname = name_obj.inc_ref().ptr();
5362
#endif
5463

@@ -63,6 +72,7 @@ inline PyTypeObject *make_static_property_type() {
6372
pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
6473

6574
setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
75+
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
6676

6777
return type;
6878
}
@@ -161,7 +171,7 @@ inline PyTypeObject* make_default_metaclass() {
161171
pybind11_fail("make_default_metaclass(): error allocating metaclass!");
162172

163173
heap_type->ht_name = name_obj.inc_ref().ptr();
164-
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
174+
#ifdef PYBIND11_BUILTIN_QUALNAME
165175
heap_type->ht_qualname = name_obj.inc_ref().ptr();
166176
#endif
167177

@@ -179,6 +189,7 @@ inline PyTypeObject* make_default_metaclass() {
179189
pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
180190

181191
setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
192+
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
182193

183194
return type;
184195
}
@@ -363,7 +374,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
363374
pybind11_fail("make_object_base_type(): error allocating type!");
364375

365376
heap_type->ht_name = name_obj.inc_ref().ptr();
366-
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
377+
#ifdef PYBIND11_BUILTIN_QUALNAME
367378
heap_type->ht_qualname = name_obj.inc_ref().ptr();
368379
#endif
369380

@@ -384,6 +395,7 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass) {
384395
pybind11_fail("PyType_Ready failed in make_object_base_type():" + error_string());
385396

386397
setattr((PyObject *) type, "__module__", str("pybind11_builtins"));
398+
PYBIND11_SET_OLDPY_QUALNAME(type, name_obj);
387399

388400
assert(!PyType_HasFeature(type, Py_TPFLAGS_HAVE_GC));
389401
return (PyObject *) heap_type;
@@ -504,13 +516,15 @@ inline void enable_buffer_protocol(PyHeapTypeObject *heap_type) {
504516
inline PyObject* make_new_python_type(const type_record &rec) {
505517
auto name = reinterpret_steal<object>(PYBIND11_FROM_STRING(rec.name));
506518

507-
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
508-
auto ht_qualname = name;
509-
if (rec.scope && hasattr(rec.scope, "__qualname__")) {
510-
ht_qualname = reinterpret_steal<object>(
519+
auto qualname = name;
520+
if (rec.scope && !PyModule_Check(rec.scope.ptr()) && hasattr(rec.scope, "__qualname__")) {
521+
#if PY_MAJOR_VERSION >= 3
522+
qualname = reinterpret_steal<object>(
511523
PyUnicode_FromFormat("%U.%U", rec.scope.attr("__qualname__").ptr(), name.ptr()));
512-
}
524+
#else
525+
qualname = str(rec.scope.attr("__qualname__").cast<std::string>() + "." + rec.name);
513526
#endif
527+
}
514528

515529
object module;
516530
if (rec.scope) {
@@ -552,8 +566,8 @@ inline PyObject* make_new_python_type(const type_record &rec) {
552566
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
553567

554568
heap_type->ht_name = name.release().ptr();
555-
#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
556-
heap_type->ht_qualname = ht_qualname.release().ptr();
569+
#ifdef PYBIND11_BUILTIN_QUALNAME
570+
heap_type->ht_qualname = qualname.inc_ref().ptr();
557571
#endif
558572

559573
auto type = &heap_type->ht_type;
@@ -599,6 +613,8 @@ inline PyObject* make_new_python_type(const type_record &rec) {
599613
if (module) // Needed by pydoc
600614
setattr((PyObject *) type, "__module__", module);
601615

616+
PYBIND11_SET_OLDPY_QUALNAME(type, qualname);
617+
602618
return (PyObject *) type;
603619
}
604620

include/pybind11/pybind11.h

+7-10
Original file line numberDiff line numberDiff line change
@@ -246,19 +246,16 @@ class cpp_function : public function {
246246
if (!t)
247247
pybind11_fail("Internal error while parsing type signature (1)");
248248
if (auto tinfo = detail::get_type_info(*t)) {
249-
#if defined(PYPY_VERSION)
250-
signature += handle((PyObject *) tinfo->type)
251-
.attr("__module__")
252-
.cast<std::string>() + ".";
253-
#endif
254-
signature += tinfo->type->tp_name;
249+
handle th((PyObject *) tinfo->type);
250+
signature +=
251+
th.attr("__module__").cast<std::string>() + "." +
252+
th.attr("__qualname__").cast<std::string>(); // Python 3.3+, but we backport it to earlier versions
255253
} else if (rec->is_new_style_constructor && arg_index == 0) {
256254
// A new-style `__init__` takes `self` as `value_and_holder`.
257255
// Rewrite it to the proper class type.
258-
#if defined(PYPY_VERSION)
259-
signature += rec->scope.attr("__module__").cast<std::string>() + ".";
260-
#endif
261-
signature += ((PyTypeObject *) rec->scope.ptr())->tp_name;
256+
signature +=
257+
rec->scope.attr("__module__").cast<std::string>() + "." +
258+
rec->scope.attr("__qualname__").cast<std::string>();
262259
} else {
263260
std::string tname(t->name());
264261
detail::clean_type_id(tname);

tests/test_class.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ TEST_SUBMODULE(class_, m) {
302302
.def(py::init<const BogusImplicitConversion &>());
303303

304304
py::implicitly_convertible<int, BogusImplicitConversion>();
305+
306+
// test_qualname
307+
// #1166: nested class docstring doesn't show nested name
308+
// Also related: tests that __qualname__ is set properly
309+
struct NestBase {};
310+
struct Nested {};
311+
py::class_<NestBase> base(m, "NestBase");
312+
base.def(py::init<>());
313+
py::class_<Nested>(base, "Nested")
314+
.def(py::init<>())
315+
.def("fn", [](Nested &, int, NestBase &, Nested &) {})
316+
.def("fa", [](Nested &, int, NestBase &, Nested &) {},
317+
"a"_a, "b"_a, "c"_a);
318+
base.def("g", [](NestBase &, Nested &) {});
319+
base.def("h", []() { return NestBase(); });
305320
}
306321

307322
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };

tests/test_class.py

+30-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,31 @@ def test_docstrings(doc):
4444
"""
4545

4646

47+
def test_qualname(doc):
48+
"""Tests that a properly qualified name is set in __qualname__ (even in pre-3.3, where we
49+
backport the attribute) and that generated docstrings properly use it and the module name"""
50+
assert m.NestBase.__qualname__ == "NestBase"
51+
assert m.NestBase.Nested.__qualname__ == "NestBase.Nested"
52+
53+
assert doc(m.NestBase.__init__) == """
54+
__init__(self: m.class_.NestBase) -> None
55+
"""
56+
assert doc(m.NestBase.g) == """
57+
g(self: m.class_.NestBase, arg0: m.class_.NestBase.Nested) -> None
58+
"""
59+
assert doc(m.NestBase.Nested.__init__) == """
60+
__init__(self: m.class_.NestBase.Nested) -> None
61+
"""
62+
assert doc(m.NestBase.Nested.fn) == """
63+
fn(self: m.class_.NestBase.Nested, arg0: int, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
64+
""" # noqa: E501 line too long
65+
assert doc(m.NestBase.Nested.fa) == """
66+
fa(self: m.class_.NestBase.Nested, a: int, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
67+
""" # noqa: E501 line too long
68+
assert m.NestBase.__module__ == "pybind11_tests.class_"
69+
assert m.NestBase.Nested.__module__ == "pybind11_tests.class_"
70+
71+
4772
def test_inheritance(msg):
4873
roger = m.Rabbit('Rabbit')
4974
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
@@ -229,7 +254,9 @@ def test_reentrant_implicit_conversion_failure(msg):
229254
# ensure that there is no runaway reentrant implicit conversion (#1035)
230255
with pytest.raises(TypeError) as excinfo:
231256
m.BogusImplicitConversion(0)
232-
assert msg(excinfo.value) == '''__init__(): incompatible constructor arguments. The following argument types are supported:
233-
1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion)
257+
assert msg(excinfo.value) == '''
258+
__init__(): incompatible constructor arguments. The following argument types are supported:
259+
1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion)
234260
235-
Invoked with: 0'''
261+
Invoked with: 0
262+
'''

0 commit comments

Comments
 (0)