Open jbms opened 3 years ago
I managed to get it working using plain CPython APIs:
PyTypeObject* GetClassGetitemMetaclass() {
#if PY_VERSION_HEX < 0x030700000
// Polyfill __class_getitem__ support for Python < 3.7
static auto* metaclass = [] {
PyTypeObject* base_metaclass =
pybind11::detail::get_internals().default_metaclass;
PyType_Slot slots[] = {
{Py_tp_base, base_metaclass},
{Py_mp_subscript,
(void*)+[](PyObject* self, PyObject* arg) -> PyObject* {
auto method = py::reinterpret_steal<py::object>(
PyObject_GetAttrString(self, "__class_getitem__"));
if (!method.ptr()) return nullptr;
return PyObject_CallFunctionObjArgs(method.ptr(), arg, nullptr);
}},
{0},
};
PyType_Spec spec = {};
spec.name = "_Metaclass";
spec.basicsize = base_metaclass->tp_basicsize;
spec.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
spec.slots = slots;
PyTypeObject* metaclass = (PyTypeObject*)PyType_FromSpec(&spec);
if (!metaclass) throw py::error_already_set();
return metaclass;
}();
return metaclass;
#else // Python version >= 3.7 supports __class_getitem__ natively.
return nullptr;
#endif
}
Is it possible to define metaclasses with pybind11 directly, though?
In this case it ended up being pretty easy to define with the CPython API directly so I suppose there isn't really a need for that.
FWIW I'm briefly trying out something semi-related for #2332; at the moment, I'm just trying to write it in pure Python, but may fall back to CPython API like you tried.
Can I ask how you passed the result of GetClassGetitemMetaclass()
to your py::class_
? Did you just cast it to py::handle
?
EDIT: I think that's about what I did, but for py::object
:
reinterpret_borrow<py::object>(py::reinterpret_cast<PyObject*>(py::detail::get_internals().default_metaclass))
Yes, I did just cast it to PyObject*
:
https://github.com/google/tensorstore/blob/24053fc2492ea958109ade321bdc8456688167f6/python/tensorstore/dim_expression.cc#L350
Hi! I'm trying to use py::metaclass
but it doesn't work for me. This question seems to ask what I need but there is no general answer.
For what I need to do, you can look at this SO question. In a few words: I'm trying to define __instancecheck__
to overload isinstance()
but I cannot define a custom metaclass.
How is it supposed to be done?
I don't believe there is any built-in support in pybind11 for defining a metaclass --- instead you have to use the Python C API directly to define the metaclass, as I did here: https://github.com/google/tensorstore/blob/24053fc2492ea958109ade321bdc8456688167f6/python/tensorstore/dim_expression.cc#L350
I see, thanks. I'm not familiar at all with the C API. Can you help me understand your code and maybe adapt it to __instancecheck__
? Also, I see you define a method contextually to the class definition. Is it possible to create the metaclass using the C API but then obtaining a py::handle
object and defining the method using pybind11
? Also see this discussion.
On the metaclass, you'd have to define tp_methods
to have an instancecheck definition, then it should work
I am aware of the
pybind11::metaclass
option that can be passed topybind11::class_
, but I can't find any documentation on how it is supposed to be used. There is a single test case here:https://github.com/pybind/pybind11/blob/028812ae7eee307dca5f8f69d467af7b92cc41c8/tests/test_methods_and_attributes.cpp#L282
but it isn't clear what that case is showing, and I haven't been able to find a single other example of code that uses
pybind11::metaclass
.What I'd like to accomplish is to make
__class_getitem__
work on Python < 3.7, equivalent to the following pure python code:Note that in Python >= 3.7, this example also works if we eliminate
Parent
and just usetype
as the metaclass ofChild
.I'd like to accomplish this same thing, where both
Parent
andChild
are defined using pybind11 rather than pure Python.My initial attempt was:
but that crashes while creating
cls_parent
: due to thet_size >= b_size
condition in the extra_ivars function in typeobject.c.