【问题标题】:Python Multiple inheritance metaclass errorPython多重继承元类错误
【发布时间】:2023-03-19 03:03:01
【问题描述】:

我正在尝试继承 from ABCctypes.Structure,但我遇到了多重继承和元类错误。

我做错了什么?

我的代码如下:

from ctypes import *
from abc import ABC, abstractmethod, ABCMeta

class FinalMeta(ABCMeta, type(Structure)):
    def __new__(mcs, name, bases, namespace, **kwargs):
        print("FinalMeta.__new__(mcs, name, bases, namespace, **kwargs)")
        print("  mcs =", mcs)
        print("  name =", name)
        print("  bases =", bases)
        print("  namespace =", namespace)
        print("  kwargs =", kwargs)
        cls = super().__new__(mcs, name, bases, namespace, **kwargs)
        print("<-- cls =", cls)
        print()
        return cls


class MessageBase(ABC, Structure, metaclass=FinalMeta):
    def __init__(self, *args, **kwargs):
        super().__init__()
    
    @property
    @abstractmethod
    def format(self):
        pass

错误:

File "D:\Development\messages.py", line 13, in __new__
    cls = super().__new__(mcs, name, bases, namespace, **kwargs)
  File "C:\Users\AppData\Local\Programs\Python\Python37-32\lib\abc.py", line 126, in __new__
    cls = super().__new__(mcls, name, bases, namespace, **kwargs)
TypeError: _ctypes.PyCStructType.__new__(FinalMeta) is not safe, use type.__new__()**

【问题讨论】:

    标签: python multiple-inheritance metaclass


    【解决方案1】:

    这行不通。 yAzou 的回答可能看起来有效,但实际上并没有——它跳过了关键的初始化并使 MessageBase 处于损坏状态,最明显的症状是即使您填写 _fields_ 并在具体子类中实现抽象属性, you still won't be able to create instances.

    尝试对用 C 编写的类型进行多重继承未得到很好的支持。部分行为可能是 Python 错误,但我怀疑它是否能正常工作。这是发生了什么。


    _ctypes.PyCStructTypectypes.Structure 的元类。这个元类是用 C 语言编写的,这意味着它的 __new__ 是作为其 C 级 tp_new 槽的包装器生成的。

    tp_new 包装器希望通过执行object.__new__(tuple)(或更糟糕的是,tuple.__new__(dict))之类的操作来确保您永远不会跳过 C 级初始化,因为这可能会导致内存损坏。因此,它有the following safety check:

    static PyObject *
    tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds)
    {
        ...
        /* Check that the use doesn't do something silly and unsafe like
           object.__new__(dict).  To do this, we check that the
           most derived base that's not a heap type is this type. */
        staticbase = subtype;
        while (staticbase && (staticbase->tp_new == slot_tp_new))
            staticbase = staticbase->tp_base;
        /* If staticbase is NULL now, it is a really weird type.
           In the spirit of backwards compatibility (?), just shut up. */
        if (staticbase && staticbase->tp_new != type->tp_new) {
            PyErr_Format(PyExc_TypeError,
                         "%s.__new__(%s) is not safe, use %s.__new__()",
                         type->tp_name,
                         subtype->tp_name,
                         staticbase->tp_name);
            return NULL;
        }
    

    这试图确保当您使用用 C 编写的 ClassA 调用 ClassA.__new__(ClassB, ...) 时,ClassA 是在 ClassB 的继承层次结构中最远的 C 类。但是,它通过tp_base 指针遵循单个基类链,忽略多重继承

    随着tp_base 指针的初始化方式(参见Objects/typeobject.c 中的best_base),跟随tp_base 指针通常会导致派生最多的C 类,但这取决于每个C 类向其(单)基类的实例内存布局。 _ctypes.PyCStructType 没有这样做(它用一个奇怪的自定义 dict 子类替换类 dict 来存储其数据),所以 FinalMeta 的 tp_base 链变为 FinalMeta->ABCMeta->type->object。

    由于_ctypes.PyCStructType 不在tp_base 链中,tp_new_wrapper 认为type 是FinalMeta 的最衍生的C 祖先,它认为你应该调用type.__new__。但是,如果您调用 type.__new__,您将跳过初始化,此检查应该阻止您跳过,从而使您的班级处于损坏状态。


    您可能认为可以通过重新排列基础以将type(Structure) 放在ABCMeta 之前来解决此问题,但这只能解决一半的问题。您将能够定义您的 MessageBase 类,但 ABCMeta.__new__ 不会运行,并且它不会执行标记您的 MessageBase 抽象所需的初始化。

    此外,还有一个完全不相关的长期问题,即使MessageBase 被标记为抽象,您仍然 能够创建它的实例。防止实例化抽象类的检查在object.__new__ 中,object 以外的 C 类(以及此类的后代)不会调用object.__new__

    【讨论】:

      猜你喜欢
      • 2021-02-14
      • 2015-04-27
      • 2011-03-01
      • 2020-12-18
      • 2021-08-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多