mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 14:45: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
|
will generally leave the C++ instance in an invalid state and cause undefined
|
||||||
behavior if the C++ instance is subsequently used.
|
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:
|
Here is an example:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -156,6 +156,31 @@ extern "C" inline PyObject *pybind11_meta_getattro(PyObject *obj, PyObject *name
|
|||||||
}
|
}
|
||||||
#endif
|
#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
|
/** 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`.
|
for static properties to function correctly. Users may override this using `py::metaclass`.
|
||||||
Return value: New reference. */
|
Return value: New reference. */
|
||||||
@ -181,6 +206,8 @@ inline PyTypeObject* make_default_metaclass() {
|
|||||||
type->tp_base = type_incref(&PyType_Type);
|
type->tp_base = type_incref(&PyType_Type);
|
||||||
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
|
||||||
|
|
||||||
|
type->tp_call = pybind11_meta_call;
|
||||||
|
|
||||||
type->tp_setattro = pybind11_meta_setattro;
|
type->tp_setattro = pybind11_meta_setattro;
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
type->tp_getattro = pybind11_meta_getattro;
|
type->tp_getattro = pybind11_meta_getattro;
|
||||||
|
@ -101,6 +101,27 @@ def test_inheritance(msg):
|
|||||||
assert "No constructor defined!" in str(excinfo.value)
|
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():
|
def test_automatic_upcasting():
|
||||||
assert type(m.return_class_1()).__name__ == "DerivedClass1"
|
assert type(m.return_class_1()).__name__ == "DerivedClass1"
|
||||||
assert type(m.return_class_2()).__name__ == "DerivedClass2"
|
assert type(m.return_class_2()).__name__ == "DerivedClass2"
|
||||||
|
Loading…
Reference in New Issue
Block a user