【问题标题】:How to define a Python metaclass from C extension?如何从 C 扩展定义 Python 元类?
【发布时间】:2021-07-08 15:38:45
【问题描述】:

在纯 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 扩展定义这个元类?

【问题讨论】:

    标签: python cpython metaclass python-extensions


    【解决方案1】:
    • 将元类定义为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-&gt;wr_object == op" failed
    • 我认为错误已修复。
    • foometa 结构没有足够的字段,有趣的是,它可能定义错误。
    • tp_init__init__ 的 C 等效项,而不是 __new____new__ 逻辑应该进入 tp_new
    猜你喜欢
    • 2019-03-28
    • 2013-06-30
    • 2011-09-18
    • 2017-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-14
    • 2021-11-22
    相关资源
    最近更新 更多