mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-21 20:55:11 +00:00
Enable static properties (py::metaclass) by default
Now that only one shared metaclass is ever allocated, it's extremely cheap to enable it for all pybind11 types. * Deprecate the default py::metaclass() since it's not needed anymore. * Allow users to specify a custom metaclass via py::metaclass(handle).
This commit is contained in:
parent
08cbe8dfed
commit
dd01665e5a
@ -437,24 +437,15 @@ The section on :ref:`properties` discussed the creation of instance properties
|
||||
that are implemented in terms of C++ getters and setters.
|
||||
|
||||
Static properties can also be created in a similar way to expose getters and
|
||||
setters of static class attributes. Two things are important to note:
|
||||
|
||||
1. Static properties are implemented by instrumenting the *metaclass* of the
|
||||
class in question -- however, this requires the class to have a modifiable
|
||||
metaclass in the first place. pybind11 provides a ``py::metaclass()``
|
||||
annotation that must be specified in the ``class_`` constructor, or any
|
||||
later method calls to ``def_{property_,∅}_{readwrite,readonly}_static`` will
|
||||
fail (see the example below).
|
||||
|
||||
2. For static properties defined in terms of setter and getter functions, note
|
||||
that the implicit ``self`` argument also exists in this case and is used to
|
||||
pass the Python ``type`` subclass instance. This parameter will often not be
|
||||
needed by the C++ side, and the following example illustrates how to
|
||||
instantiate a lambda getter function that ignores it:
|
||||
setters of static class attributes. Note that the implicit ``self`` argument
|
||||
also exists in this case and is used to pass the Python ``type`` subclass
|
||||
instance. This parameter will often not be needed by the C++ side, and the
|
||||
following example illustrates how to instantiate a lambda getter function
|
||||
that ignores it:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
py::class_<Foo>(m, "Foo", py::metaclass())
|
||||
py::class_<Foo>(m, "Foo")
|
||||
.def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); });
|
||||
|
||||
Operator overloading
|
||||
|
@ -54,7 +54,15 @@ struct dynamic_attr { };
|
||||
struct buffer_protocol { };
|
||||
|
||||
/// Annotation which requests that a special metaclass is created for a type
|
||||
struct metaclass { };
|
||||
struct metaclass {
|
||||
handle value;
|
||||
|
||||
PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.")
|
||||
metaclass() = default;
|
||||
|
||||
/// Override pybind11's default metaclass
|
||||
explicit metaclass(handle value) : value(value) { }
|
||||
};
|
||||
|
||||
/// Annotation to mark enums as an arithmetic type
|
||||
struct arithmetic { };
|
||||
@ -149,8 +157,7 @@ struct function_record {
|
||||
/// Special data structure which (temporarily) holds metadata about a bound class
|
||||
struct type_record {
|
||||
PYBIND11_NOINLINE type_record()
|
||||
: multiple_inheritance(false), dynamic_attr(false),
|
||||
buffer_protocol(false), metaclass(false) { }
|
||||
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false) { }
|
||||
|
||||
/// Handle to the parent scope
|
||||
handle scope;
|
||||
@ -179,6 +186,9 @@ struct type_record {
|
||||
/// Optional docstring
|
||||
const char *doc = nullptr;
|
||||
|
||||
/// Custom metaclass (optional)
|
||||
handle metaclass;
|
||||
|
||||
/// Multiple inheritance marker
|
||||
bool multiple_inheritance : 1;
|
||||
|
||||
@ -188,9 +198,6 @@ struct type_record {
|
||||
/// Does the class implement the buffer protocol?
|
||||
bool buffer_protocol : 1;
|
||||
|
||||
/// Does the class require its own metaclass?
|
||||
bool metaclass : 1;
|
||||
|
||||
/// Is the default (unique_ptr) holder type used?
|
||||
bool default_holder : 1;
|
||||
|
||||
@ -356,7 +363,7 @@ struct process_attribute<buffer_protocol> : process_attribute_default<buffer_pro
|
||||
|
||||
template <>
|
||||
struct process_attribute<metaclass> : process_attribute_default<metaclass> {
|
||||
static void init(const metaclass &, type_record *r) { r->metaclass = true; }
|
||||
static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; }
|
||||
};
|
||||
|
||||
|
||||
|
@ -244,7 +244,8 @@ inline PyObject *make_object_base_type(size_t instance_size) {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
auto metaclass = get_internals().default_metaclass;
|
||||
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
|
||||
if (!heap_type)
|
||||
pybind11_fail("make_object_base_type(): error allocating type!");
|
||||
|
||||
@ -437,7 +438,10 @@ inline PyObject* make_new_python_type(const type_record &rec) {
|
||||
issue no Python C API calls which could potentially invoke the
|
||||
garbage collector (the GC will call type_traverse(), which will in
|
||||
turn find the newly constructed type in an invalid state) */
|
||||
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
|
||||
auto metaclass = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr()
|
||||
: internals.default_metaclass;
|
||||
|
||||
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
|
||||
if (!heap_type)
|
||||
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
|
||||
|
||||
@ -457,12 +461,6 @@ inline PyObject* make_new_python_type(const type_record &rec) {
|
||||
/* Don't inherit base __init__ */
|
||||
type->tp_init = pybind11_object_init;
|
||||
|
||||
/* Custom metaclass if requested (used for static properties) */
|
||||
if (rec.metaclass) {
|
||||
Py_INCREF(internals.default_metaclass);
|
||||
Py_TYPE(type) = (PyTypeObject *) internals.default_metaclass;
|
||||
}
|
||||
|
||||
/* Supported protocols */
|
||||
type->tp_as_number = &heap_type->as_number;
|
||||
type->tp_as_sequence = &heap_type->as_sequence;
|
||||
|
@ -829,18 +829,6 @@ protected:
|
||||
const auto is_static = !(rec_fget->is_method && rec_fget->scope);
|
||||
const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
|
||||
|
||||
if (is_static) {
|
||||
auto mclass = handle((PyObject *) Py_TYPE(m_ptr));
|
||||
|
||||
if ((PyTypeObject *) mclass.ptr() == &PyType_Type)
|
||||
pybind11_fail(
|
||||
"Adding static properties to the type '" +
|
||||
std::string(((PyTypeObject *) m_ptr)->tp_name) +
|
||||
"' requires the type to have a custom metaclass. Please "
|
||||
"ensure that one is created by supplying the pybind11::metaclass() "
|
||||
"annotation to the associated class_<>(..) invocation.");
|
||||
}
|
||||
|
||||
auto property = handle((PyObject *) (is_static ? get_internals().static_property_type
|
||||
: &PyProperty_Type));
|
||||
attr(name) = property(fget.ptr() ? fget : none(),
|
||||
|
@ -202,7 +202,7 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
.def("__str__", &ExampleMandA::toString)
|
||||
.def_readwrite("value", &ExampleMandA::value);
|
||||
|
||||
py::class_<TestProperties>(m, "TestProperties", py::metaclass())
|
||||
py::class_<TestProperties>(m, "TestProperties")
|
||||
.def(py::init<>())
|
||||
.def_readonly("def_readonly", &TestProperties::value)
|
||||
.def_readwrite("def_readwrite", &TestProperties::value)
|
||||
@ -228,7 +228,7 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
auto static_set2 = [](py::object, int v) { TestPropRVP::sv2.value = v; };
|
||||
auto rvp_copy = py::return_value_policy::copy;
|
||||
|
||||
py::class_<TestPropRVP>(m, "TestPropRVP", py::metaclass())
|
||||
py::class_<TestPropRVP>(m, "TestPropRVP")
|
||||
.def(py::init<>())
|
||||
.def_property_readonly("ro_ref", &TestPropRVP::get1)
|
||||
.def_property_readonly("ro_copy", &TestPropRVP::get2, rvp_copy)
|
||||
@ -245,6 +245,10 @@ test_initializer methods_and_attributes([](py::module &m) {
|
||||
.def_property_readonly("rvalue", &TestPropRVP::get_rvalue)
|
||||
.def_property_readonly_static("static_rvalue", [](py::object) { return SimpleValue(); });
|
||||
|
||||
struct MetaclassOverride { };
|
||||
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass((PyObject *) &PyType_Type))
|
||||
.def_property_readonly_static("readonly", [](py::object) { return 1; });
|
||||
|
||||
#if !defined(PYPY_VERSION)
|
||||
py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr())
|
||||
.def(py::init());
|
||||
|
@ -126,6 +126,22 @@ def test_static_cls():
|
||||
instance.static_cls = check_self
|
||||
|
||||
|
||||
def test_metaclass_override():
|
||||
"""Overriding pybind11's default metaclass changes the behavior of `static_property`"""
|
||||
from pybind11_tests import MetaclassOverride
|
||||
|
||||
assert type(ExampleMandA).__name__ == "pybind11_type"
|
||||
assert type(MetaclassOverride).__name__ == "type"
|
||||
|
||||
assert MetaclassOverride.readonly == 1
|
||||
assert type(MetaclassOverride.__dict__["readonly"]).__name__ == "pybind11_static_property"
|
||||
|
||||
# Regular `type` replaces the property instead of calling `__set__()`
|
||||
MetaclassOverride.readonly = 2
|
||||
assert MetaclassOverride.readonly == 2
|
||||
assert isinstance(MetaclassOverride.__dict__["readonly"], int)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
|
||||
def test_property_return_value_policies(access):
|
||||
from pybind11_tests import TestPropRVP
|
||||
|
@ -123,24 +123,24 @@ test_initializer mi_static_properties([](py::module &pm) {
|
||||
.def(py::init<>())
|
||||
.def("vanilla", &Vanilla::vanilla);
|
||||
|
||||
py::class_<WithStatic1>(m, "WithStatic1", py::metaclass())
|
||||
py::class_<WithStatic1>(m, "WithStatic1")
|
||||
.def(py::init<>())
|
||||
.def_static("static_func1", &WithStatic1::static_func1)
|
||||
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
|
||||
|
||||
py::class_<WithStatic2>(m, "WithStatic2", py::metaclass())
|
||||
py::class_<WithStatic2>(m, "WithStatic2")
|
||||
.def(py::init<>())
|
||||
.def_static("static_func2", &WithStatic2::static_func2)
|
||||
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
|
||||
|
||||
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
|
||||
m, "VanillaStaticMix1", py::metaclass())
|
||||
m, "VanillaStaticMix1")
|
||||
.def(py::init<>())
|
||||
.def_static("static_func", &VanillaStaticMix1::static_func)
|
||||
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
|
||||
|
||||
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
|
||||
m, "VanillaStaticMix2", py::metaclass())
|
||||
m, "VanillaStaticMix2")
|
||||
.def(py::init<>())
|
||||
.def_static("static_func", &VanillaStaticMix2::static_func)
|
||||
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
|
||||
|
@ -190,7 +190,7 @@ struct MoveOutContainer {
|
||||
test_initializer python_types([](py::module &m) {
|
||||
/* No constructor is explicitly defined below. An exception is raised when
|
||||
trying to construct it directly from Python */
|
||||
py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation", py::metaclass())
|
||||
py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation")
|
||||
.def("get_dict", &ExamplePythonTypes::get_dict, "Return a Python dictionary")
|
||||
.def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary")
|
||||
.def("get_list", &ExamplePythonTypes::get_list, "Return a Python list")
|
||||
|
Loading…
Reference in New Issue
Block a user