Skip to content

Commit 394b69b

Browse files
committed
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).
1 parent 2c67767 commit 394b69b

8 files changed

+53
-49
lines changed

docs/advanced/classes.rst

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -437,24 +437,15 @@ The section on :ref:`properties` discussed the creation of instance properties
437437
that are implemented in terms of C++ getters and setters.
438438

439439
Static properties can also be created in a similar way to expose getters and
440-
setters of static class attributes. Two things are important to note:
441-
442-
1. Static properties are implemented by instrumenting the *metaclass* of the
443-
class in question -- however, this requires the class to have a modifiable
444-
metaclass in the first place. pybind11 provides a ``py::metaclass()``
445-
annotation that must be specified in the ``class_`` constructor, or any
446-
later method calls to ``def_{property_,∅}_{readwrite,readonly}_static`` will
447-
fail (see the example below).
448-
449-
2. For static properties defined in terms of setter and getter functions, note
450-
that the implicit ``self`` argument also exists in this case and is used to
451-
pass the Python ``type`` subclass instance. This parameter will often not be
452-
needed by the C++ side, and the following example illustrates how to
453-
instantiate a lambda getter function that ignores it:
440+
setters of static class attributes. Note that the implicit ``self`` argument
441+
also exists in this case and is used to pass the Python ``type`` subclass
442+
instance. This parameter will often not be needed by the C++ side, and the
443+
following example illustrates how to instantiate a lambda getter function
444+
that ignores it:
454445

455446
.. code-block:: cpp
456447
457-
py::class_<Foo>(m, "Foo", py::metaclass())
448+
py::class_<Foo>(m, "Foo")
458449
.def_property_readonly_static("foo", [](py::object /* self */) { return Foo(); });
459450
460451
Operator overloading

include/pybind11/attr.h

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,15 @@ struct dynamic_attr { };
5454
struct buffer_protocol { };
5555

5656
/// Annotation which requests that a special metaclass is created for a type
57-
struct metaclass { };
57+
struct metaclass {
58+
handle value;
59+
60+
PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.")
61+
metaclass() = default;
62+
63+
/// Override pybind11's default metaclass
64+
explicit metaclass(handle value) : value(value) { }
65+
};
5866

5967
/// Annotation to mark enums as an arithmetic type
6068
struct arithmetic { };
@@ -149,8 +157,7 @@ struct function_record {
149157
/// Special data structure which (temporarily) holds metadata about a bound class
150158
struct type_record {
151159
PYBIND11_NOINLINE type_record()
152-
: multiple_inheritance(false), dynamic_attr(false),
153-
buffer_protocol(false), metaclass(false) { }
160+
: multiple_inheritance(false), dynamic_attr(false), buffer_protocol(false) { }
154161

155162
/// Handle to the parent scope
156163
handle scope;
@@ -179,6 +186,9 @@ struct type_record {
179186
/// Optional docstring
180187
const char *doc = nullptr;
181188

189+
/// Custom metaclass (optional)
190+
handle metaclass;
191+
182192
/// Multiple inheritance marker
183193
bool multiple_inheritance : 1;
184194

@@ -188,9 +198,6 @@ struct type_record {
188198
/// Does the class implement the buffer protocol?
189199
bool buffer_protocol : 1;
190200

191-
/// Does the class require its own metaclass?
192-
bool metaclass : 1;
193-
194201
/// Is the default (unique_ptr) holder type used?
195202
bool default_holder : 1;
196203

@@ -356,7 +363,7 @@ struct process_attribute<buffer_protocol> : process_attribute_default<buffer_pro
356363

357364
template <>
358365
struct process_attribute<metaclass> : process_attribute_default<metaclass> {
359-
static void init(const metaclass &, type_record *r) { r->metaclass = true; }
366+
static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; }
360367
};
361368

362369

include/pybind11/class_support.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,8 @@ inline PyObject *make_object_base_type(size_t instance_size) {
244244
issue no Python C API calls which could potentially invoke the
245245
garbage collector (the GC will call type_traverse(), which will in
246246
turn find the newly constructed type in an invalid state) */
247-
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
247+
auto metaclass = get_internals().default_metaclass;
248+
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
248249
if (!heap_type)
249250
pybind11_fail("make_object_base_type(): error allocating type!");
250251

@@ -437,7 +438,10 @@ inline PyObject* make_new_python_type(const type_record &rec) {
437438
issue no Python C API calls which could potentially invoke the
438439
garbage collector (the GC will call type_traverse(), which will in
439440
turn find the newly constructed type in an invalid state) */
440-
auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
441+
auto metaclass = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr()
442+
: internals.default_metaclass;
443+
444+
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
441445
if (!heap_type)
442446
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");
443447

@@ -457,12 +461,6 @@ inline PyObject* make_new_python_type(const type_record &rec) {
457461
/* Don't inherit base __init__ */
458462
type->tp_init = pybind11_object_init;
459463

460-
/* Custom metaclass if requested (used for static properties) */
461-
if (rec.metaclass) {
462-
Py_INCREF(internals.default_metaclass);
463-
Py_TYPE(type) = (PyTypeObject *) internals.default_metaclass;
464-
}
465-
466464
/* Supported protocols */
467465
type->tp_as_number = &heap_type->as_number;
468466
type->tp_as_sequence = &heap_type->as_sequence;

include/pybind11/pybind11.h

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -829,18 +829,6 @@ class generic_type : public object {
829829
const auto is_static = !(rec_fget->is_method && rec_fget->scope);
830830
const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
831831

832-
if (is_static) {
833-
auto mclass = handle((PyObject *) Py_TYPE(m_ptr));
834-
835-
if ((PyTypeObject *) mclass.ptr() == &PyType_Type)
836-
pybind11_fail(
837-
"Adding static properties to the type '" +
838-
std::string(((PyTypeObject *) m_ptr)->tp_name) +
839-
"' requires the type to have a custom metaclass. Please "
840-
"ensure that one is created by supplying the pybind11::metaclass() "
841-
"annotation to the associated class_<>(..) invocation.");
842-
}
843-
844832
auto property = handle((PyObject *) (is_static ? get_internals().static_property_type
845833
: &PyProperty_Type));
846834
attr(name) = property(fget.ptr() ? fget : none(),

tests/test_methods_and_attributes.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ test_initializer methods_and_attributes([](py::module &m) {
202202
.def("__str__", &ExampleMandA::toString)
203203
.def_readwrite("value", &ExampleMandA::value);
204204

205-
py::class_<TestProperties>(m, "TestProperties", py::metaclass())
205+
py::class_<TestProperties>(m, "TestProperties")
206206
.def(py::init<>())
207207
.def_readonly("def_readonly", &TestProperties::value)
208208
.def_readwrite("def_readwrite", &TestProperties::value)
@@ -228,7 +228,7 @@ test_initializer methods_and_attributes([](py::module &m) {
228228
auto static_set2 = [](py::object, int v) { TestPropRVP::sv2.value = v; };
229229
auto rvp_copy = py::return_value_policy::copy;
230230

231-
py::class_<TestPropRVP>(m, "TestPropRVP", py::metaclass())
231+
py::class_<TestPropRVP>(m, "TestPropRVP")
232232
.def(py::init<>())
233233
.def_property_readonly("ro_ref", &TestPropRVP::get1)
234234
.def_property_readonly("ro_copy", &TestPropRVP::get2, rvp_copy)
@@ -245,6 +245,10 @@ test_initializer methods_and_attributes([](py::module &m) {
245245
.def_property_readonly("rvalue", &TestPropRVP::get_rvalue)
246246
.def_property_readonly_static("static_rvalue", [](py::object) { return SimpleValue(); });
247247

248+
struct MetaclassOverride { };
249+
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass((PyObject *) &PyType_Type))
250+
.def_property_readonly_static("readonly", [](py::object) { return 1; });
251+
248252
#if !defined(PYPY_VERSION)
249253
py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr())
250254
.def(py::init());

tests/test_methods_and_attributes.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ def check_self(self):
126126
instance.static_cls = check_self
127127

128128

129+
def test_metaclass_override():
130+
"""Overriding pybind11's default metaclass changes the behavior of `static_property`"""
131+
from pybind11_tests import MetaclassOverride
132+
133+
assert type(ExampleMandA).__name__ == "pybind11_type"
134+
assert type(MetaclassOverride).__name__ == "type"
135+
136+
assert MetaclassOverride.readonly == 1
137+
assert type(MetaclassOverride.__dict__["readonly"]).__name__ == "pybind11_static_property"
138+
139+
# Regular `type` replaces the property instead of calling `__set__()`
140+
MetaclassOverride.readonly = 2
141+
assert MetaclassOverride.readonly == 2
142+
assert isinstance(MetaclassOverride.__dict__["readonly"], int)
143+
144+
129145
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
130146
def test_property_return_value_policies(access):
131147
from pybind11_tests import TestPropRVP

tests/test_multiple_inheritance.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,24 +123,24 @@ test_initializer mi_static_properties([](py::module &pm) {
123123
.def(py::init<>())
124124
.def("vanilla", &Vanilla::vanilla);
125125

126-
py::class_<WithStatic1>(m, "WithStatic1", py::metaclass())
126+
py::class_<WithStatic1>(m, "WithStatic1")
127127
.def(py::init<>())
128128
.def_static("static_func1", &WithStatic1::static_func1)
129129
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
130130

131-
py::class_<WithStatic2>(m, "WithStatic2", py::metaclass())
131+
py::class_<WithStatic2>(m, "WithStatic2")
132132
.def(py::init<>())
133133
.def_static("static_func2", &WithStatic2::static_func2)
134134
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
135135

136136
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(
137-
m, "VanillaStaticMix1", py::metaclass())
137+
m, "VanillaStaticMix1")
138138
.def(py::init<>())
139139
.def_static("static_func", &VanillaStaticMix1::static_func)
140140
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
141141

142142
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(
143-
m, "VanillaStaticMix2", py::metaclass())
143+
m, "VanillaStaticMix2")
144144
.def(py::init<>())
145145
.def_static("static_func", &VanillaStaticMix2::static_func)
146146
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);

tests/test_python_types.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ struct MoveOutContainer {
190190
test_initializer python_types([](py::module &m) {
191191
/* No constructor is explicitly defined below. An exception is raised when
192192
trying to construct it directly from Python */
193-
py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation", py::metaclass())
193+
py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation")
194194
.def("get_dict", &ExamplePythonTypes::get_dict, "Return a Python dictionary")
195195
.def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary")
196196
.def("get_list", &ExamplePythonTypes::get_list, "Return a Python list")

0 commit comments

Comments
 (0)