Skip to content

Commit 49aa611

Browse files
committed
Improved errors when inheriting and forgetting to call __init__
If the base class doesn't have a constructor, tell the user that it cannot be inherited from. Caveats: * Doesn't work on PyPy * If the default instance base was no longer a PyHeapTypeObject this could crash. * If pybind11_object_init changed in the future, and multiple pybind11 modules are present, get_internals might return an old version of the function
1 parent c676036 commit 49aa611

File tree

2 files changed

+24
-3
lines changed

2 files changed

+24
-3
lines changed

include/pybind11/detail/class.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,14 @@ extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, P
171171
// Ensure that the base __init__ function(s) were called
172172
for (const auto &vh : values_and_holders(instance)) {
173173
if (!vh.holder_constructed()) {
174-
PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__",
175-
vh.type->type->tp_name);
174+
auto message = "%.200s.__init__() must be called when overriding __init__";
175+
#if !defined(PYPY_VERSION)
176+
auto default_init = ((PyHeapTypeObject*)get_internals().instance_base)->ht_type.tp_init;
177+
if (vh.type->type->tp_init == default_init) {
178+
message = "%.200s has no __init__ and cannot be used as a base class from Python";
179+
}
180+
#endif
181+
PyErr_Format(PyExc_TypeError, message, vh.type->type->tp_name);
176182
Py_DECREF(self);
177183
return nullptr;
178184
}
@@ -620,7 +626,7 @@ inline PyObject* make_new_python_type(const type_record &rec) {
620626
type->tp_bases = bases.release().ptr();
621627

622628
/* Don't inherit base __init__ */
623-
type->tp_init = pybind11_object_init;
629+
type->tp_init = ((PyHeapTypeObject*)internals.instance_base)->ht_type.tp_init;
624630

625631
/* Supported protocols */
626632
type->tp_as_number = &heap_type->as_number;

tests/test_class.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,21 @@ def __init__(self):
132132
expected = "m.class_.Hamster.__init__() must be called when overriding __init__"
133133
assert msg(exc_info.value) == expected
134134

135+
# Base doesn't have __init__
136+
class ChChimera(m.Chimera):
137+
def __init__(self):
138+
pass
139+
140+
with pytest.raises(TypeError) as exc_info:
141+
ChChimera()
142+
if env.PYPY:
143+
# can't detect no __init__ in PyPy
144+
expected = "Chimera.__init__() must be called when overriding __init__"
145+
else:
146+
expected = \
147+
"m.class_.Chimera has no __init__ and cannot be used as a base class from Python"
148+
assert msg(exc_info.value) == expected
149+
135150

136151
def test_automatic_upcasting():
137152
assert type(m.return_class_1()).__name__ == "DerivedClass1"

0 commit comments

Comments
 (0)