Skip to content

Commit 1a8e2d0

Browse files
committed
Better error message when inheriting from a type without a constructor
1 parent 4493751 commit 1a8e2d0

File tree

3 files changed

+23
-3
lines changed

3 files changed

+23
-3
lines changed

include/pybind11/detail/class.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,10 @@ 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 = (vh.type->type->tp_init == get_internals().default_object_init()) ?
175+
"%.200s has no __init__ and cannot be used as a base class from Python" :
176+
"%.200s.__init__() must be called when overriding __init__";
177+
PyErr_Format(PyExc_TypeError, message, vh.type->type->tp_name);
176178
Py_DECREF(self);
177179
return nullptr;
178180
}
@@ -302,6 +304,8 @@ extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *,
302304
/// An `__init__` function constructs the C++ object. Users should provide at least one
303305
/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the
304306
/// following default function will be used which simply throws an exception.
307+
///
308+
/// Use `get_internals().default_object_init()` instead of using this function directly
305309
extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
306310
PyTypeObject *type = Py_TYPE(self);
307311
std::string msg;
@@ -620,7 +624,7 @@ inline PyObject* make_new_python_type(const type_record &rec) {
620624
type->tp_bases = bases.release().ptr();
621625

622626
/* Don't inherit base __init__ */
623-
type->tp_init = pybind11_object_init;
627+
type->tp_init = internals.default_object_init();
624628

625629
/* Supported protocols */
626630
type->tp_as_number = &heap_type->as_number;

include/pybind11/detail/internals.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ struct internals {
121121
PYBIND11_TLS_FREE(tstate);
122122
}
123123
#endif
124+
125+
inline initproc default_object_init() {
126+
auto heap_type = (PyHeapTypeObject*)instance_base;
127+
return heap_type->ht_type.tp_init;
128+
}
124129
};
125130

126131
/// Additional type information which does not fit into the PyTypeObject.

tests/test_class.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,17 @@ def __init__(self):
128128
"Hamster.__init__() must be called when overriding __init__"] # PyPy
129129
assert msg(exc_info.value) in expected
130130

131+
# Base doesn't have __init__
132+
class ChChimera(m.Chimera):
133+
def __init__(self):
134+
pass
135+
136+
with pytest.raises(TypeError) as exc_info:
137+
ChChimera()
138+
expected = ["m.class_.Chimera has no __init__ and cannot be used as a base class from Python",
139+
"Chimera has no __init__ and cannot be used as a base class from Python"] # PyPy
140+
assert msg(exc_info.value) in expected
141+
131142

132143
def test_automatic_upcasting():
133144
assert type(m.return_class_1()).__name__ == "DerivedClass1"

0 commit comments

Comments
 (0)