Add is_final to disallow inheritance from Python

- Not currently supported on PyPy
This commit is contained in:
Dustin Spicuzza 2020-04-05 02:34:00 -04:00 committed by Wenzel Jakob
parent b14aeb7cfa
commit 0dfffcf257
5 changed files with 67 additions and 2 deletions

View File

@ -1042,6 +1042,32 @@ described trampoline:
``.def("foo", static_cast<int (A::*)() const>(&Publicist::foo));``
where ``int (A::*)() const`` is the type of ``A::foo``.
Binding final classes
=====================
Some classes may not be appropriate to inherit from. In C++11, classes can
use the ``final`` specifier to ensure that a class cannot be inherited from.
The ``py::is_final`` attribute can be used to ensure that Python classes
cannot inherit from a specified type. The underlying C++ type does not need
to be declared final.
.. code-block:: cpp
class IsFinal final {};
py::class_<IsFinal>(m, "IsFinal", py::is_final());
When you try to inherit from such a class in Python, you will now get this
error:
.. code-block:: pycon
>>> class PyFinalChild(IsFinal):
... pass
TypeError: type 'IsFinal' is not an acceptable base type
.. note:: This attribute is currently ignored on PyPy
Custom automatic downcasters
============================

View File

@ -23,6 +23,9 @@ struct is_method { handle class_; is_method(const handle &c) : class_(c) { } };
/// Annotation for operators
struct is_operator { };
/// Annotation for classes that cannot be subclassed
struct is_final { };
/// Annotation for parent scope
struct scope { handle value; scope(const handle &s) : value(s) { } };
@ -201,7 +204,7 @@ struct function_record {
struct type_record {
PYBIND11_NOINLINE type_record()
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false),
default_holder(true), module_local(false) { }
default_holder(true), module_local(false), is_final(false) { }
/// Handle to the parent scope
handle scope;
@ -254,6 +257,9 @@ struct type_record {
/// Is the class definition local to the module shared object?
bool module_local : 1;
/// Is the class inheritable from python classes?
bool is_final : 1;
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *)) {
auto base_info = detail::get_type_info(base, false);
if (!base_info) {
@ -416,6 +422,11 @@ struct process_attribute<dynamic_attr> : process_attribute_default<dynamic_attr>
static void init(const dynamic_attr &, type_record *r) { r->dynamic_attr = true; }
};
template <>
struct process_attribute<is_final> : process_attribute_default<is_final> {
static void init(const is_final &, type_record *r) { r->is_final = true; }
};
template <>
struct process_attribute<buffer_protocol> : process_attribute_default<buffer_protocol> {
static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; }

View File

@ -604,10 +604,12 @@ inline PyObject* make_new_python_type(const type_record &rec) {
#endif
/* Flags */
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
type->tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE;
#if PY_MAJOR_VERSION < 3
type->tp_flags |= Py_TPFLAGS_CHECKTYPES;
#endif
if (!rec.is_final)
type->tp_flags |= Py_TPFLAGS_BASETYPE;
if (rec.dynamic_attr)
enable_dynamic_attributes(heap_type);

View File

@ -367,6 +367,14 @@ TEST_SUBMODULE(class_, m) {
.def(py::init<>())
.def("ptr", &Aligned::ptr);
#endif
// test_final
struct IsFinal final {};
py::class_<IsFinal>(m, "IsFinal", py::is_final());
// test_non_final_final
struct IsNonFinalFinal {};
py::class_<IsNonFinalFinal>(m, "IsNonFinalFinal", py::is_final());
}
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };

View File

@ -279,3 +279,21 @@ def test_aligned():
if hasattr(m, "Aligned"):
p = m.Aligned().ptr()
assert p % 1024 == 0
# https://bitbucket.org/pypy/pypy/issues/2742
@pytest.unsupported_on_pypy
def test_final():
with pytest.raises(TypeError) as exc_info:
class PyFinalChild(m.IsFinal):
pass
assert str(exc_info.value).endswith("is not an acceptable base type")
# https://bitbucket.org/pypy/pypy/issues/2742
@pytest.unsupported_on_pypy
def test_non_final_final():
with pytest.raises(TypeError) as exc_info:
class PyNonFinalFinalChild(m.IsNonFinalFinal):
pass
assert str(exc_info.value).endswith("is not an acceptable base type")