问题
I have the following python code:
class Meta(type):
def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
# Only do checks for subclasses
if cls.__name__ == 'Parent':
return obj
required_attrs = ['x']
for ra in required_attrs:
if ra not in dir(obj):
fmt = 'Subclasses of Parent must define the %s attribute'
raise NotImplementedError(fmt % ra)
return obj
class Parent(metaclass=Meta):
pass
class Child(Parent):
def __init__(self):
self.x = True
Meta is used only to require that Child defines certain attributes. This class structure must remain as is because this is how my project is structured. Parent is actually called DefaultConfig and Child is actually a user-defined class derived from DefaultConfig.
I'm working on translating Meta and Parent into a C extension. This is the module:
#include <Python.h>
#include <structmember.h>
#define ARRLEN(x) sizeof(x)/sizeof(x[0])
typedef struct {
PyObject_HEAD
} MetaObject;
typedef struct {
PyObject_HEAD
} ParentObject;
static PyObject *Meta_call(MetaObject *type, PyObject *args, PyObject *kwargs) {
PyObject *obj = PyType_GenericNew((PyTypeObject *) type, args, kwargs);
// Only do checks for subclasses of Parent
if (strcmp(obj->ob_type->tp_name, "Parent") == 0)
return obj;
// Get obj's attributes
PyObject *obj_dir = PyObject_Dir(obj);
if (obj_dir == NULL)
return NULL;
char *required_attrs[] = {"x"};
// Raise an exception of obj doesn't define all required_attrs
PyObject *attr_obj;
int has_attr;
for (int i=0; i<ARRLEN(required_attrs); i++) {
attr_obj = PyUnicode_FromString(required_attrs[i]);
has_attr = PySequence_Contains(obj_dir, attr_obj);
if (has_attr == 0) {
printf("Subclasses of Parent must define %s\n", required_attrs[i]);
// raise NotImplementedError
return NULL;
} else if (has_attr == -1) {
return NULL;
}
}
return obj;
}
static PyTypeObject MetaType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Meta",
.tp_basicsize = sizeof(MetaObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_call = (ternaryfunc) Meta_call,
};
static PyTypeObject ParentType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Parent",
.tp_basicsize = sizeof(ParentObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
};
static PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_custom(void) {
PyObject *module = PyModule_Create(&custommodule);
if (module == NULL)
return NULL;
// Should Parent inherit from Meta?
ParentType.tp_base = &MetaType;
if (PyType_Ready(&MetaType) < 0)
return NULL;
Py_INCREF(&MetaType);
PyModule_AddObject(module, "Meta", (PyObject *) &MetaType);
if (PyType_Ready(&ParentType) < 0)
return NULL;
Py_INCREF(&ParentType);
PyModule_AddObject(module, "Parent", (PyObject *) &ParentType);
return module;
}
This is the python code used to test module custom:
import custom
class Child(custom.Parent):
def __init__(self):
self.x = True
if __name__ == '__main__':
c = Child()
Unfortunately, there is no .tp_meta member in the PyTypeObject struct, so how do I specify Meta as the metaclass of Parent?
EDIT:
Modified C code:
#include <Python.h>
#include <structmember.h>
#define ARRLEN(x) sizeof(x)/sizeof(x[0])
typedef struct {
PyObject_HEAD
PyTypeObject base;
} MetaObject;
typedef struct {
PyObject_HEAD
} ParentObject;
static PyObject *Meta_call(MetaObject *type, PyObject *args, PyObject *kwargs) {
PyObject *obj = PyType_GenericNew((PyTypeObject *) type, args, kwargs);
// Only do checks for subclasses of Parent
if (strcmp(obj->ob_type->tp_name, "Parent") == 0)
return obj;
// Get obj's attributes
PyObject *obj_dir = PyObject_Dir(obj);
if (obj_dir == NULL)
return NULL;
char *required_attrs[] = {"x"};
// Raise an exception of obj doesn't define all required_attrs
PyObject *attr_obj;
int has_attr;
for (int i=0; i<ARRLEN(required_attrs); i++) {
attr_obj = PyUnicode_FromString(required_attrs[i]);
has_attr = PySequence_Contains(obj_dir, attr_obj);
if (has_attr == 0) {
printf("Subclasses of Parent must define %s\n", required_attrs[i]);
// raise NotImplementedError
return NULL;
} else if (has_attr == -1) {
return NULL;
}
}
return obj;
}
static PyTypeObject MetaType = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "custom.Meta",
.tp_basicsize = sizeof(MetaObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
.tp_call = (ternaryfunc) Meta_call,
};
static PyTypeObject ParentType = {
PyVarObject_HEAD_INIT(&MetaType, 0)
.tp_name = "custom.Parent",
.tp_basicsize = sizeof(ParentObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = PyType_GenericNew,
};
static PyModuleDef custommodule = {
PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_size = -1,
};
PyMODINIT_FUNC PyInit_custom(void) {
PyObject *module = PyModule_Create(&custommodule);
if (module == NULL)
return NULL;
MetaType.tp_base = &PyType_Type;
if (PyType_Ready(&MetaType) < 0)
return NULL;
Py_INCREF(&MetaType);
PyModule_AddObject(module, "Meta", (PyObject *) &MetaType);
if (PyType_Ready(&ParentType) < 0)
return NULL;
Py_INCREF(&ParentType);
PyModule_AddObject(module, "Parent", (PyObject *) &ParentType);
return module;
}
回答1:
The metaclass is nothing but a type that is used as the type (ob_type!) of the class (type)... (clear, isn't it)... ParentType does not inherit from MetaType but is an instance of `MetaType.
Hence, the place where &MetaType should go if it works as it should, is ParentType.ob_type:
PyModule_AddObject(module, "Meta", (PyObject *) &MetaType);
ParentType.ob_type = &MetaType;
if (PyType_Ready(&ParentType) < 0)
PyType_Ready checks the ob_type field - if it is NULL, it takes the ob_type of the .tp_base; but if ob_type is set already, it is left as is.
Actually you can set it in the ParentType initializer:
PyVarObject_HEAD_INIT(&MetaType, 0)
The first argument goes to the ob_type field.
回答2:
There is no direct way to do this. According to the py docs, there is no members or flags to directly indicate a class is a meta class of another. The attribute responsible for indicating a meta class is inside the class dictionary. You could implement something that modifies the .tp_dict member, but this is actually deemed unsafe if done through the dictionary C-API.
Warning It is not safe to use PyDict_SetItem() on or otherwise modify tp_dict with the dictionary C-API.
EDIT:
From the python source code, it seems meta class is accessed as an id via the C dictionary API, but the methods to do so are prefixed with an _, and don't appear in any documentation.
meta = _PyDict_GetItemId(mkw, &PyId_metaclass);
if (meta != NULL) {
Py_INCREF(meta);
if (_PyDict_DelItemId(mkw, &PyId_metaclass) < 0) {
Py_DECREF(meta);
Py_DECREF(mkw);
Py_DECREF(bases);
return NULL;
}
These methods are apart of the "limited api", and can be used by defining the Py_LIMITED_API macro
PyAPI_FUNC(PyObject *) _PyDict_GetItemId(PyObject *dp, struct _Py_Identifier *key);
#endif /* !Py_LIMITED_API */
PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, const char *key, PyObject *item);
#ifndef Py_LIMITED_API
PyAPI_FUNC(int) _PyDict_SetItemId(PyObject *dp, struct _Py_Identifier *key, PyObject *item);
#endif /* !Py_LIMITED_API */
来源:https://stackoverflow.com/questions/52957192/python-extension-in-c-metaclass