使用 ABC、PolymorphicModel、django-models 会产生元类冲突

Posted

技术标签:

【中文标题】使用 ABC、PolymorphicModel、django-models 会产生元类冲突【英文标题】:Using ABC, PolymorphicModel, django-models gives metaclass conflict 【发布时间】:2018-04-29 09:52:51 【问题描述】:

到目前为止,关于 SO 的所有其他答案都以完全相同的方式回答:构建您的元类,然后继承这些元类的“加入”版本,即

class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass

class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass

但我不知道这些人生活在什么世界,他们在哪里构建您自己的元类!显然,一个人会使用其他库中的类,除非你对元编程有完美的处理,否则你怎么知道你是否可以覆盖一个类的元类? (显然我还没有掌握它们)。

我的问题是:

class InterfaceToTransactions(ABC):
    def account(self):
        return None
    ...

class Category(PolymorphicModel, InterfaceToTransactions):
    def account(self):
        return self.source_account
    ...

class Income(TimeStampedModel, InterfaceToTransactions):
    def account(self):
        return self.destination_account
    ...

这当然给了我错误:“元类冲突:派生类的元类必须是其所有基类的元类的(非严格)子类” 我已经尝试了上面给出的解决方案的许多变体,以下不起作用,给出了同样的错误。

class InterfaceToTransactionsIntermediaryMeta(type(PolymorphicModel), type(InterfaceToTransactions)):
pass

class Category(PolymorphicModel, InterfaceToTransactions):
    __metaclass__ = InterfaceToTransactionsIntermediaryMeta
    ...

也没有在类 Meta 函数中添加任何内容。我已阅读有关此主题的所有其他 SO 问题,请不要简单地将其标记为重复。

-------在接受解决方案后于 1/8/18 编辑-------

奇怪的是,如果我尝试使用这个新配置(我接受的配置)进行迁移,它会再次开始给出元类错误,但它在运行时仍然有效。如果我注释掉元类部分然后 makemigrations 并迁移,它会成功执行,但是每次迁移后我都必须将它放回那里。

【问题讨论】:

只是为了澄清,您需要它使您的CategoryIncome 都继承自InterfaceToTransactions,其中抽象方法account 是?如果仅此而已,使用 Metaclass 有点矫枉过正...... 我已经缩短了这里的定义,但是对于每个使用接口的类来说,都有相当多的冗长方法。 @mastachimp 你找到解决migrate/makemigrations 问题的方法了吗? @mastachimp 我注意到如果您在新数据库上运行 python manage.py migrate 没有问题,但随后的运行会中断。 这似乎是 Django 中的一个已知错误,但他们尚未就一个好的解决方案达成一致:code.djangoproject.com/ticket/25068 【参考方案1】:

如果您使用的是 Python 3,那么您正在尝试错误地使用您的派生元类。

既然你得到“同样的错误”,而不是其他可能的、更微妙的错误,我会说这就是正在发生的事情。

试着改成:

class IntermediaryMeta(type(InterfaceToTransactions), type(PolymorphicModel)):
    pass

class Category(PolymorphicModel, InterfaceToTransactions, metaclass=IntermediaryMeta):
    ...

(至少 ABCMeta 类可以保证使用super 协同工作,这是将类放在基础上的足够动机) 元组)

如果这给您带来了新的和改进的错误,这意味着这些类中的一个或两个由于多种动机之一而无法真正正确地协作。然后,要采取的方法是强制依赖于 ABCMeta 的继承树不要这样做,因为它的角色在一种语言中几乎是美学的,在这种语言中,其他一切都是为了“同意的成年人”,比如 Python。

不幸的是,方法是使用各种蛮力方法,从安全的“重写所有内容”到猴子修补 ABCMeta 和抽象方法的地方,“InterfaceToTransactions”被定义为什么都不做。

如果您需要到达那里并需要一些帮助,请发布另一个问题。

抱歉 - 这实际上是使用元类的主要缺点。

【讨论】:

这行得通,包括使用 TimeStampedModel,当然我必须使用 TSM 而不是多态创建一个单独但结构相同的元类。有趣的是,唯一重要的是让它与 py3 兼容,即“metaclass=IntermediaryMeta”而不是 py2 样式。【参考方案2】:

除非 django-polymorphic 决定从 abc.ABC 继承,否则这将很难实现。一个好的解决方案是“手动”创建您的界面。例如:

class InterfaceToTransactions:
    def account(self):
        raise NotImplementedError("Account method must be implemented.")
    ...

class Category(PolymorphicModel, InterfaceToTransactions):
    def account(self):
        return self.source_account
    ...

class Income(TimeStampedModel, InterfaceToTransactions):
    def account(self):
        return self.destination_account
    ...

【讨论】:

以上是关于使用 ABC、PolymorphicModel、django-models 会产生元类冲突的主要内容,如果未能解决你的问题,请参考以下文章

路由的路径模式

把windows中的D区挂载到LINUX中的/abc目录下

csharp ABC115D.cs

cf 1082abc

ABC291题解(D-G)

添加啥特殊字符使两个电子邮件地址相同(例如:添加 . 使 abc@d.com == ab.c@d.com)?