如何从C扩展定义Python元类?

3

在纯Python中,定义和使用元类相对简单。

class Meta(type):
    def __new__(cls, name, bases, dict):
        x = super().__new__(cls, name, bases, dict)
        print("I'm called on class construction time!")
        return x

class A(metaclass=Meta):
    pass

class B(A):
    pass

如何从Python C扩展中定义此元类?

1个回答

3
  • 将元类定义为PyType_Type的子类 (tp_base)
  • __new__逻辑放置在tp_init
  • 通过在PyVarObject_HEAD_INIT中引用该元类,将其应用于其他类
#include <Python.h>
#include <iostream>

struct foometa {
  PyTypeObject head;
};

int foometa_init(foometa *cls, PyObject *args, PyObject *kwargs) {
  if (PyType_Type.tp_init((PyObject*)cls, args, kwargs) < 0) {
    return -1;
  }
  std::cerr << "I'm called on class construction time!\n";
  return 0;
}

#define DEFERRED_ADDRESS(ADDR) nullptr

static PyTypeObject foometa_type = {
    PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
    "demo.foometa",
    0,
    0,
    0,                                          /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                           /* tp_methods */
    0,                                          /* tp_members */
    0,                           /* tp_getset */
    DEFERRED_ADDRESS(&PyType_Type),             /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)foometa_init,                    /* tp_init */
    0,                                          /* tp_alloc */
    0                                           /* tp_new */
};

PyObject *fooparent_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  PyObject* obj = type->tp_alloc(type, 0);
  return obj;
}

static PyTypeObject fooparent_type = {
    PyVarObject_HEAD_INIT(&foometa_type, 0)
    "demo.fooparent",
    sizeof(PyObject),
    0,
    0,                                          /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    0,                                          /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    0,                           /* tp_methods */
    0,                                          /* tp_members */
    0,                           /* tp_getset */
    0,             /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                    /* tp_init */
    0,                                          /* tp_alloc */
    fooparent_new                                           /* tp_new */
};

int
demo_init(PyObject *m) {
  foometa_type.tp_base = &PyType_Type;
  if (PyType_Ready(&foometa_type) < 0) {
    return -1;
  }
  if (PyType_Ready(&fooparent_type) < 0) {
    return -1;
  }

  Py_INCREF(&foometa_type);
  if (PyModule_AddObject(m, "foometa",
                         (PyObject *) &foometa_type) < 0)
      return -1;

  Py_INCREF(&fooparent_type);
  if (PyModule_AddObject(m, "fooparent",
                         (PyObject *) &fooparent_type) < 0)
      return -1;
  return 0;
}

static PyModuleDef demomodule = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Example module",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_demo() {
  PyObject* m = PyModule_Create(&demomodule);
  if (m == nullptr) return nullptr;
  if (demo_init(m) < 0) return nullptr;
  return m;
}

完整的可运行示例请参见https://github.com/ezyang/cpython-metaclass


这段代码存在一个错误,如果你从fooparent两次进行子类化,你将无法通过断言Modules/gcmodule.c:714: handle_weakrefs: Assertion "wr->wr_object == op" failed - Edward Z. Yang
错误已经修复,我想。 - Edward Z. Yang
有趣的是,foometa 结构体没有足够的字段,所以可能定义错误了。 - Edward Z. Yang
tp_init__init__ 的 C 语言等效版本,而不是 __new____new__ 的逻辑应该放在 tp_new 中。 - user2357112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接