mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 06:35:12 +00:00
Throw TypeError when subclasses forget to call __init__ (#2152)
- Fixes #2103
This commit is contained in:
parent
d54d6d8c61
commit
1b0bf352fa
@ -149,6 +149,11 @@ memory for the C++ portion of the instance will be left uninitialized, which
|
||||
will generally leave the C++ instance in an invalid state and cause undefined
|
||||
behavior if the C++ instance is subsequently used.
|
||||
|
||||
.. versionadded:: 2.5.1
|
||||
|
||||
The default pybind11 metaclass will throw a ``TypeError`` when it detects
|
||||
that ``__init__`` was not called by a derived class.
|
||||
|
||||
Here is an example:
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -156,6 +156,31 @@ extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name
|
||||
}
|
||||
#endif
|
||||
|
||||
/// metaclass `__call__` function that is used to create all pybind11 objects.
|
||||
extern "C" inline PyObject *pybind11_meta_call(PyObject *type, PyObject *args, PyObject *kwargs) {
|
||||
|
||||
// use the default metaclass call to create/initialize the object
|
||||
PyObject *self = PyType_Type.tp_call(type, args, kwargs);
|
||||
if (self == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// This must be a pybind11 instance
|
||||
auto instance = reinterpret_cast<detail::instance *>(self);
|
||||
|
||||
// Ensure that the base __init__ function(s) were called
|
||||
for (auto vh : values_and_holders(instance)) {
|
||||
if (!vh.holder_constructed()) {
|
||||
PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__",
|
||||
vh.type->type->tp_name);
|
||||
Py_DECREF(self);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/** This metaclass is assigned by default to all pybind11 types and is required in order
|
||||
for static properties to function correctly. Users may override this using `py::metaclass`.
|
||||
Return value: New reference. */
|
||||
@ -181,6 +206,8 @@ inline PyTypeObject* make_default_metaclass() {
|
||||
type->tp_base = type_incref(&PyType_Type);
|
||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||
|
||||
type->tp_call = pybind11_meta_call;
|
||||
|
||||
type->tp_setattro = pybind11_meta_setattro;
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
type->tp_getattro = pybind11_meta_getattro;
|
||||
|
@ -101,6 +101,27 @@ def test_inheritance(msg):
|
||||
assert "No constructor defined!" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_inheritance_init(msg):
|
||||
|
||||
# Single base
|
||||
class Python(m.Pet):
|
||||
def __init__(self):
|
||||
pass
|
||||
with pytest.raises(TypeError) as exc_info:
|
||||
Python()
|
||||
assert msg(exc_info.value) == "m.class_.Pet.__init__() must be called when overriding __init__"
|
||||
|
||||
# Multiple bases
|
||||
class RabbitHamster(m.Rabbit, m.Hamster):
|
||||
def __init__(self):
|
||||
m.Rabbit.__init__(self, "RabbitHamster")
|
||||
|
||||
with pytest.raises(TypeError) as exc_info:
|
||||
RabbitHamster()
|
||||
expected = "m.class_.Hamster.__init__() must be called when overriding __init__"
|
||||
assert msg(exc_info.value) == expected
|
||||
|
||||
|
||||
def test_automatic_upcasting():
|
||||
assert type(m.return_class_1()).__name__ == "DerivedClass1"
|
||||
assert type(m.return_class_2()).__name__ == "DerivedClass2"
|
||||
|
Loading…
Reference in New Issue
Block a user