【问题标题】:Using Protocols in django models raises metaclass conflict error在 django 模型中使用协议会引发元类冲突错误
【发布时间】:2021-08-11 22:48:25
【问题描述】:

假设我有一个名为 Summable 的 PEP-544 协议:

class Summable(Protocol):
    @property
    total_amount()-> Decimal:
      ...

我有实现Protocol的模型Item

class Item(Summable, models.Model):
    discount = models.DecimalField(
        decimal_places=2,
        validators=[MaxValueValidator(1)],
        default=Decimal('0.00'),
        max_digits=10
    )
    price = models.DecimalField(
        decimal_places=4,
        validators=[MinValueValidator(0)],
        max_digits=10
    )

    @property
    def total_amount(self) - > Decimal:
       return self.price - self.price * self.discount

    class Meta:
        ordering = ['id']

我明白了:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

即使我从 Summable.Meta 和 models.Model.Meta 扩展 Item 的 Meta 也会发生同样的情况。

我正在使用 python 3.9 有什么想法吗?

【问题讨论】:

  • 注意Item 没有实现Summable 协议! total_amount: Decimal 意味着可读的和可写的属性。一个普通的 @property 只能读取。
  • 非常有趣的一点
  • @MisterMiyagi 那么您对协议中只读字段的建议是我目前的更正吗?即用@property 声明它
  • 感谢您发现只是在玩打字 python,这一切都很迷人 xD
  • 最简单的解决方法是在协议中也将total_amount 定义为@property

标签: python python-3.x django protocols pep


【解决方案1】:

嗯,有很多陷阱:

  1. 您需要创建一个新的元类:

例如:

class ModelProtocolMeta(type(Model), type(Protocol)):
     pass
  1. 您需要将协议放在最后,这样协议就不会用 no_init 覆盖模型的构造函数。 协议的 no_init 构造函数如下:
def _no_init(self, *args, **kwargs):
    if type(self)._is_protocol:
        raise TypeError('Protocols cannot be instantiated')

所以它只会默默地覆盖构造函数而不会出现任何错误,因为继承的类会将 _is_protocol 设置为 False

(注意super没有被调用,所以我们说的是完全覆盖)

所以最终我们需要以下内容:

class Item(models.Model, Summable, metaclass=ModelProtocolMeta):
    discount = models.DecimalField(
        decimal_places=2,
        validators=[MaxValueValidator(1)],
        default=Decimal('0.00'),
        max_digits=10
    )
    price = models.DecimalField(
        decimal_places=4,
        validators=[MinValueValidator(0)],
        max_digits=10
    )

    @property
    def total_amount(self) -> Decimal:
       return sel.price - self.price * self.discount

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-13
    • 2017-06-19
    • 1970-01-01
    • 2014-03-01
    • 2015-06-05
    • 2016-02-07
    • 2013-03-25
    相关资源
    最近更新 更多