Skip to content

Commit dbdb24a

Browse files
committed
Throw TypeError when subclasses forget to call __init__
- Based on pybind/pybind11#2152 - Fixes #1210
1 parent d145ec5 commit dbdb24a

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

src/nb_type.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,33 @@ static int nb_type_init(PyObject *self, PyObject *args, PyObject *kwds) {
498498
return 0;
499499
}
500500

501+
/// metaclass `__call__` function that is used to create all nanobind objects
502+
static PyObject *nb_type_call(PyObject *type, PyObject *args, PyObject *kwargs) {
503+
504+
// use the default metaclass call to create/initialize the object
505+
#if defined(Py_LIMITED_API)
506+
PyObject *self = ((ternaryfunc)PyType_GetSlot(&PyType_Type, Py_tp_call))(type, args, kwargs);
507+
#else
508+
PyObject *self = PyType_Type.tp_call(type, args, kwargs);
509+
#endif
510+
if (self == nullptr) {
511+
return nullptr;
512+
}
513+
514+
// This must be a nanobind instance
515+
nb_inst *inst = (nb_inst *) self;
516+
if (inst->state == nb_inst::state_uninitialized) {
517+
const type_data *t = nb_type_data(Py_TYPE(self));
518+
PyErr_Format(PyExc_TypeError,
519+
"%.200s.__init__() must be called when overriding __init__",
520+
t->name);
521+
Py_DECREF(self);
522+
return nullptr;
523+
}
524+
525+
return self;
526+
}
527+
501528
/// Special case to handle 'Class.property = value' assignments
502529
int nb_type_setattro(PyObject* obj, PyObject* name, PyObject* value) {
503530
nb_internals *int_p = internals;
@@ -861,6 +888,7 @@ static PyTypeObject *nb_type_tp(size_t supplement) noexcept {
861888
{ Py_tp_dealloc, (void *) nb_type_dealloc },
862889
{ Py_tp_setattro, (void *) nb_type_setattro },
863890
{ Py_tp_init, (void *) nb_type_init },
891+
{ Py_tp_call, (void *) nb_type_call },
864892
#if defined(Py_LIMITED_API)
865893
{ Py_tp_members, (void *) members },
866894
#endif

tests/test_classes.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,13 +256,9 @@ def name(self):
256256
def what(self):
257257
return "b"
258258

259-
with pytest.warns(
260-
RuntimeWarning,
261-
match="nanobind: attempted to access an uninitialized instance of type",
262-
):
263-
with pytest.raises(TypeError) as excinfo:
264-
t.go(Incomplete2())
265-
assert "incompatible function arguments" in str(excinfo.value)
259+
with pytest.raises(TypeError) as excinfo:
260+
Incomplete2()
261+
assert "__init__() must be called when overriding __init__" in str(excinfo.value)
266262

267263

268264
def test12_large_pointers():

0 commit comments

Comments
 (0)