mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05: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.
|
that are implemented in terms of C++ getters and setters.
|
||||||
|
|
||||||
Static properties can also be created in a similar way to expose getters and
|
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:
|
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
|
||||||
1. Static properties are implemented by instrumenting the *metaclass* of the
|
instance. This parameter will often not be needed by the C++ side, and the
|
||||||
class in question -- however, this requires the class to have a modifiable
|
following example illustrates how to instantiate a lambda getter function
|
||||||
metaclass in the first place. pybind11 provides a ``py::metaclass()``
|
that ignores it:
|
||||||
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:
|
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. 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(); });
|
.def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); });
|
||||||
|
|
||||||
Operator overloading
|
Operator overloading
|
||||||
|
@ -54,7 +54,15 @@ struct dynamic_attr { };
|
|||||||
struct buffer_protocol { };
|
struct buffer_protocol { };
|
||||||
|
|
||||||
/// Annotation which requests that a special metaclass is created for a type
|
/// 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
|
/// Annotation to mark enums as an arithmetic type
|
||||||
struct arithmetic { };
|
struct arithmetic { };
|
||||||
@ -149,8 +157,7 @@ struct function_record {
|
|||||||
/// Special data structure which (temporarily) holds metadata about a bound class
|
/// Special data structure which (temporarily) holds metadata about a bound class
|
||||||
struct type_record {
|
struct type_record {
|
||||||
PYBIND11_NOINLINE type_record()
|
PYBIND11_NOINLINE type_record()
|
||||||
: multiple_inheritance(false), dynamic_attr(false),
|
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false) { }
|
||||||
buffer_protocol(false), metaclass(false) { }
|
|
||||||
|
|
||||||
/// Handle to the parent scope
|
/// Handle to the parent scope
|
||||||
handle scope;
|
handle scope;
|
||||||
@ -179,6 +186,9 @@ struct type_record {
|
|||||||
/// Optional docstring
|
/// Optional docstring
|
||||||
const char *doc = nullptr;
|
const char *doc = nullptr;
|
||||||
|
|
||||||
|
/// Custom metaclass (optional)
|
||||||
|
handle metaclass;
|
||||||
|
|
||||||
/// Multiple inheritance marker
|
/// Multiple inheritance marker
|
||||||
bool multiple_inheritance : 1;
|
bool multiple_inheritance : 1;
|
||||||
|
|
||||||
@ -188,9 +198,6 @@ struct type_record {
|
|||||||
/// Does the class implement the buffer protocol?
|
/// Does the class implement the buffer protocol?
|
||||||
bool buffer_protocol : 1;
|
bool buffer_protocol : 1;
|
||||||
|
|
||||||
/// Does the class require its own metaclass?
|
|
||||||
bool metaclass : 1;
|
|
||||||
|
|
||||||
/// Is the default (unique_ptr) holder type used?
|
/// Is the default (unique_ptr) holder type used?
|
||||||
bool default_holder : 1;
|
bool default_holder : 1;
|
||||||
|
|
||||||
@ -356,7 +363,7 @@ struct process_attribute<buffer_protocol> : process_attribute_default<buffer_pro
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct process_attribute<metaclass> : process_attribute_default<metaclass> {
|
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
|
issue no Python C API calls which could potentially invoke the
|
||||||
garbage collector (the GC will call type_traverse(), which will in
|
garbage collector (the GC will call type_traverse(), which will in
|
||||||
turn find the newly constructed type in an invalid state) */
|
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)
|
if (!heap_type)
|
||||||
pybind11_fail("make_object_base_type(): error allocating 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
|
issue no Python C API calls which could potentially invoke the
|
||||||
garbage collector (the GC will call type_traverse(), which will in
|
garbage collector (the GC will call type_traverse(), which will in
|
||||||
turn find the newly constructed type in an invalid state) */
|
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)
|
if (!heap_type)
|
||||||
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
|
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__ */
|
/* Don't inherit base __init__ */
|
||||||
type->tp_init = pybind11_object_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 */
|
/* Supported protocols */
|
||||||
type->tp_as_number = &heap_type->as_number;
|
type->tp_as_number = &heap_type->as_number;
|
||||||
type->tp_as_sequence = &heap_type->as_sequence;
|
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 is_static = !(rec_fget->is_method && rec_fget->scope);
|
||||||
const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
|
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
|
auto property = handle((PyObject *) (is_static ? get_internals().static_property_type
|
||||||
: &PyProperty_Type));
|
: &PyProperty_Type));
|
||||||
attr(name) = property(fget.ptr() ? fget : none(),
|
attr(name) = property(fget.ptr() ? fget : none(),
|
||||||
|
@ -202,7 +202,7 @@ test_initializer methods_and_attributes([](py::module &m) {
|
|||||||
.def("__str__", &ExampleMandA::toString)
|
.def("__str__", &ExampleMandA::toString)
|
||||||
.def_readwrite("value", &ExampleMandA::value);
|
.def_readwrite("value", &ExampleMandA::value);
|
||||||
|
|
||||||
py::class_<TestProperties>(m, "TestProperties", py::metaclass())
|
py::class_<TestProperties>(m, "TestProperties")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_readonly("def_readonly", &TestProperties::value)
|
.def_readonly("def_readonly", &TestProperties::value)
|
||||||
.def_readwrite("def_readwrite", &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 static_set2 = [](py::object, int v) { TestPropRVP::sv2.value = v; };
|
||||||
auto rvp_copy = py::return_value_policy::copy;
|
auto rvp_copy = py::return_value_policy::copy;
|
||||||
|
|
||||||
py::class_<TestPropRVP>(m, "TestPropRVP", py::metaclass())
|
py::class_<TestPropRVP>(m, "TestPropRVP")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_property_readonly("ro_ref", &TestPropRVP::get1)
|
.def_property_readonly("ro_ref", &TestPropRVP::get1)
|
||||||
.def_property_readonly("ro_copy", &TestPropRVP::get2, rvp_copy)
|
.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("rvalue", &TestPropRVP::get_rvalue)
|
||||||
.def_property_readonly_static("static_rvalue", [](py::object) { return SimpleValue(); });
|
.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)
|
#if !defined(PYPY_VERSION)
|
||||||
py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr())
|
py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr())
|
||||||
.def(py::init());
|
.def(py::init());
|
||||||
|
@ -126,6 +126,22 @@ def test_static_cls():
|
|||||||
instance.static_cls = check_self
|
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"])
|
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
|
||||||
def test_property_return_value_policies(access):
|
def test_property_return_value_policies(access):
|
||||||
from pybind11_tests import TestPropRVP
|
from pybind11_tests import TestPropRVP
|
||||||
|
@ -123,24 +123,24 @@ test_initializer mi_static_properties([](py::module &pm) {
|
|||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def("vanilla", &Vanilla::vanilla);
|
.def("vanilla", &Vanilla::vanilla);
|
||||||
|
|
||||||
py::class_<WithStatic1>(m, "WithStatic1", py::metaclass())
|
py::class_<WithStatic1>(m, "WithStatic1")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_static("static_func1", &WithStatic1::static_func1)
|
.def_static("static_func1", &WithStatic1::static_func1)
|
||||||
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
|
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
|
||||||
|
|
||||||
py::class_<WithStatic2>(m, "WithStatic2", py::metaclass())
|
py::class_<WithStatic2>(m, "WithStatic2")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_static("static_func2", &WithStatic2::static_func2)
|
.def_static("static_func2", &WithStatic2::static_func2)
|
||||||
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
|
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
|
||||||
|
|
||||||
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
|
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
|
||||||
m, "VanillaStaticMix1", py::metaclass())
|
m, "VanillaStaticMix1")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_static("static_func", &VanillaStaticMix1::static_func)
|
.def_static("static_func", &VanillaStaticMix1::static_func)
|
||||||
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
|
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
|
||||||
|
|
||||||
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
|
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
|
||||||
m, "VanillaStaticMix2", py::metaclass())
|
m, "VanillaStaticMix2")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def_static("static_func", &VanillaStaticMix2::static_func)
|
.def_static("static_func", &VanillaStaticMix2::static_func)
|
||||||
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
|
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
|
||||||
|
@ -190,7 +190,7 @@ struct MoveOutContainer {
|
|||||||
test_initializer python_types([](py::module &m) {
|
test_initializer python_types([](py::module &m) {
|
||||||
/* No constructor is explicitly defined below. An exception is raised when
|
/* No constructor is explicitly defined below. An exception is raised when
|
||||||
trying to construct it directly from Python */
|
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", &ExamplePythonTypes::get_dict, "Return a Python dictionary")
|
||||||
.def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary")
|
.def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary")
|
||||||
.def("get_list", &ExamplePythonTypes::get_list, "Return a Python list")
|
.def("get_list", &ExamplePythonTypes::get_list, "Return a Python list")
|
||||||
|
Loading…
Reference in New Issue
Block a user