【问题标题】:Django: Custom Metaclass Inheriting From And Extending `ModelBase`Django:继承和扩展“ModelBase”的自定义元类
【发布时间】:2018-06-10 01:35:34
【问题描述】:

我正在尝试做一些元类恶作剧。我想要我自己的元类 从ModelBase 继承,然后我想添加额外的逻辑 扩展其__new__ 方法。不过我觉得有些东西 在我使用它的方式中,MRO/继承顺序发生了奇怪的事情。

基本情况如下:

from django.db.models import Model, ModelBase


class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        # As I am trying to extend `ModelBase`, I was expecting this
        # call to `super` to give me the return value from here:

        # https://github.com/django/django/blob/master/django/db/models/base.py#L300

        # And that I would be able to access everyhing in `_meta` with
        # `clsobj._meta`. But actually this object is
        # `MyAbstractModel` and has no `_meta` property so I'm pretty
        # sure `__new__` isn't being called on `ModelBase` at all at
        # this point.
        clsobj = super().__new__(cls, name, bases, attrs)

        # Now, I want to have access to the `_meta` property setup by
        # `ModelBase` so I can dispatch on the data in there. For
        # example, let's do something with the field definitions.
        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj


class MyAbstractModel(metaclass=CustomMetaclass):
    """This model is abstract because I only want the custom metaclass
    logic to apply to those models of my choosing and I don't want to
    be able to instantiate it directly. See the class definitions below.
    """
    class Meta:
        abstract = True

class MyModel(Model):
    """Regular model, will be derived from metaclass `ModelBase` as usual.
    """
    pass

class MyCustomisedModel(MyAbstractModel):
    """This model should enjoy the logic defined by our extended `__new__` method.
    """
    pass

知道为什么ModelBase 上的__new__ 没有被调用 CustomMetaClass?如何以这种方式正确扩展ModelBase?我很确定元类继承是可能的 但似乎我错过了什么......

【问题讨论】:

  • 您的代码看起来不错,只是您需要在__new__ 中返回clsobj
  • 是的,很好,我实际上正在这样做,但为简洁起见,我省略了它;我已将其添加到上面。但是它没有回答这个问题,因为我已经表明我需要在我的自定义 __new__ 方法返回任何内容之前从 ModelBase 中的 __new__ 返回对象。
  • 是的。您在CustomMetaclass .__new__ 中的评论说明了一切。 ModelBase.__new__ 被调用,只是访问未设置的 _meta 属性会破坏它,因为 ModelBase.__new__ returns early when there are no bases
  • 您可以做的就是将模型声明为基础。例如MyAbstractModel(Model, metaclass=CustomMetaclass)
  • @OluwafemiSule 在早期回归中表现出色,我确实忽略了这一点。添加Model 不会像你建议的那样工作,但是因为AppRegistry 没有加载。虽然我认为我有一个解决方案,但我会尽快将其作为完整答案发布。

标签: python django inheritance metaclass method-resolution-order


【解决方案1】:

使用_meta 属性获取clsobj 的方法很简单:

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta.get_fields():
            do_stuff_with_fields()

        return clsobj

我们可以用MyAbstractModel(Model, metaclass=CustomMetaclass)做同样的事情。

但是,这里的最终成功仍然取决于我们打算在__new__ 方法中进行的工作类型。如果我们想通过元编程以某种方式自省和处理类的字段,我们需要知道我们正在尝试在 import 时使用__new__ 重写类,因此(因为这是 Django) app registry 尚未准备好,如果出现某些情况(例如,我们被禁止访问或使用反向关系),这可能会导致引发异常。即使将Model 作为基础传递给__new__,也会发生这种情况。

我们可以通过使用以下对_get_fields 的非公开调用(Django 在某些地方自己执行)来半规避其中的一些问题:

class CustomMetaclass(ModelBase):
    def __new__(cls, name, bases, attrs):
        bases = (Model,)
        clsobj = super().__new__(cls, name, bases, attrs)

        for field in clsobj._meta._get_fields(reverse=False):
            do_stuff_with_fields()

        return clsobj

但是根据场景和我们试图实现的目标,我们可能仍然会遇到问题;例如,我们将无法使用我们的元类访问任何反向关系。所以还是不行。

为了克服这个限制,我们必须利用应用注册表中的信号来使我们的类像我们希望的那样动态,并且可以完全访问_meta.get_fields

看到这张票:https://code.djangoproject.com/ticket/24231

主要内容是:“Django 模型类不允许您在准备好的应用注册表的上下文之外使用。”

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-21
    相关资源
    最近更新 更多