Skip to content

Commit 69a91ab

Browse files
committed
Allow users to specify a custom metaclass
1 parent 2b96543 commit 69a91ab

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

include/pybind11/attr.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,15 @@ struct dynamic_attr { };
5353
/// Annotation which enables the buffer protocol for a type
5454
struct buffer_protocol { };
5555

56-
/// DEPRECATED: Annotation which requests that a special metaclass is created for a type
56+
/// Annotation which requests that a special metaclass is created for a type
5757
struct metaclass {
58+
handle value;
59+
5860
PYBIND11_DEPRECATED("py::metaclass() is no longer required. It's turned on by default now.")
5961
metaclass() = default;
62+
63+
/// Override pybind11's default metaclass
64+
metaclass(handle value) : value(value) { }
6065
};
6166

6267
/// Annotation to mark enums as an arithmetic type
@@ -181,6 +186,9 @@ struct type_record {
181186
/// Optional docstring
182187
const char *doc = nullptr;
183188

189+
/// Custom metaclass (optional)
190+
handle metaclass;
191+
184192
/// Multiple inheritance marker
185193
bool multiple_inheritance : 1;
186194

@@ -353,8 +361,10 @@ struct process_attribute<buffer_protocol> : process_attribute_default<buffer_pro
353361
static void init(const buffer_protocol &, type_record *r) { r->buffer_protocol = true; }
354362
};
355363

356-
// DEPRECATED
357-
template <> struct process_attribute<metaclass> : process_attribute_default<metaclass> { };
364+
template <>
365+
struct process_attribute<metaclass> : process_attribute_default<metaclass> {
366+
static void init(const metaclass &m, type_record *r) { r->metaclass = m.value; }
367+
};
358368

359369
/// Process an 'arithmetic' attribute for enums (does nothing here)
360370
template <>

include/pybind11/class_support.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,9 @@ inline PyObject* make_new_python_type(const type_record &rec) {
366366
issue no Python C API calls which could potentially invoke the
367367
garbage collector (the GC will call type_traverse(), which will in
368368
turn find the newly constructed type in an invalid state) */
369-
auto metaclass = internals.default_metaclass;
369+
auto metaclass = rec.metaclass.ptr() ? (PyTypeObject *) rec.metaclass.ptr()
370+
: internals.default_metaclass;
371+
370372
auto heap_type = (PyHeapTypeObject *) metaclass->tp_alloc(metaclass, 0);
371373
if (!heap_type)
372374
pybind11_fail(std::string(rec.name) + ": Unable to create type object!");

tests/test_methods_and_attributes.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)