【问题标题】:How MRO, super works in pythonMRO,超级如何在 python 中工作
【发布时间】:2018-08-25 02:37:01
【问题描述】:

有人可以帮我理解 MRO 在 python 中的工作原理吗? 假设我有四个职业——角色、盗贼、敏捷、偷偷摸摸。 Character 是 Thief 的超类,Agile 和 Sneaky 是兄弟姐妹。请在下面查看我的代码和问题

class Character:
    def __init__(self, name="", **kwargs):
        if not name:
            raise ValueError("'name' is required")
        self.name = name

        for key, value in kwargs.items():
            setattr(self, key, value)


class Agile:
    agile = True

    def __init__(self, agile=True, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.agile = agile


class Sneaky:
    sneaky = True

    def __init__(self, sneaky=True, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sneaky = sneaky


class Thief(Agile, Sneaky, Character):
    def pickpocket(self):
    return self.sneaky and bool(random.randint(0, 1))


parker = Thief(name="Parker", sneaky=False)

所以,这就是我的想法,如果我理解正确,请告诉我。

由于敏捷在列表中排在首位,所有参数首先发送到敏捷,其中参数将与敏捷参数交叉引用。如果匹配,则将分配该值,然后将没有匹配关键字的所有内容打包在 *kwargs 中并发送到 Sneaky 类(通过 super),在那里会发生同样的事情 - 所有参数都被解包,与 Sneaky 参数交叉引用(这是在设置了偷偷摸摸 = False 时),然后打包在 kwargs 中并发送到 Character。然后 Character inint 方法中的所有内容都将运行并设置所有值(如 name = "Parker")。

我认为 MRO 在回来的路上是如何工作的

既然所有内容都进入了 Character 类,并且 Character init 方法中的所有内容都已运行,现在它必须返回到 Agile 和 Sneaky 类并完成运行它们 init 方法中的所有内容(或它们的 super 下的所有内容)。所以,它会先回到 Sneaky 类并完成它的 init 方法,然后再回到 Agile 类并完成它的其余 init 方法(分别)。

我是否在任何地方都感到困惑?呸。对不起,我知道这很多,但我真的被困在这里,我正在努力清楚地了解 MRO 的工作原理。

谢谢大家。

【问题讨论】:

  • 此代码未按发布的方式运行。至少有一个缩进错误,类乱序,……这意味着无法调试您的代码来帮助回答您的问题。
  • @ReblochonMasque 实际上,在这种协作多重继承中使用的 mixin 调用 super 是完全合理的。毕竟,任何方法到达Sneaky 的唯一途径是来自Agile 调用super
  • @ReblochonMasque 我不知道任何具体的事情,因为它结合了两个独立的想法,这些想法通常很复杂,可以有自己的单独教程。通常,任何用super 解释协作式 MI 的人都会将示例重点放在菱形层次结构上,例如 this one,而任何解释 mixin 的人,例如 this answer,如果超出了基础知识,可能会继续学习以下课程同时是一个mixin和一个ABC,比如collections.abcs…
  • @ReblochonMasque 但是这两个概念很好地结合在一起。一个打算与合作层次结构一起使用的mixin,如果它需要覆盖__init__(或其他任何东西),即使它没有继承任何东西,它也必须super它。 (当然这意味着 mixin 只能与该层次结构一起使用,但这很好;即使没有任何 super,您也不会将 werkzeug.AcceptMixin 与非werkzeug 类一起使用。
  • @ReblochonMasque 实际上,这只是举了一个例子:大多数 werkzeug mixin 方法并没有覆盖任何需要链接的东西,但有些是,比如ETagResponseMixin.freeze,它们使用super 这样做。这意味着ETagResponseMixin 只能在最终基类(或至少某个祖先)具有freeze 方法的层次结构中使用它可以super,但这很好,因为它不打算在其他任何地方使用.

标签: python super method-resolution-order


【解决方案1】:

您发布的代码甚至无法编译,更不用说运行了。但是,猜测它应该如何工作......

是的,您基本上做对了。

但您应该能够通过两种方式自己验证这一点。并且知道如何验证它可能比知道答案更重要。


首先,打印出Thief.mro()。它应该看起来像这样:

[Thief, Agile, Sneaky, Character, object]

然后您可以看到哪些类提供了__init__ 方法,因此如果每个人都只调用super,它们将如何被链接起来:

>>> [cls for cls in Thief.mro() if '__init__' in cls.__dict__]
[Agile, Sneaky, Character, object]

而且,只是为了确保 Agile 确实首先被调用:

>>> Thief.__init__
<function Agile.__init__>

其次,您可以在调试器中运行代码并逐步执行调用。

或者您可以在每个语句的顶部和底部添加 print 语句,如下所示:

def __init__(self, agile=True, *args, **kwargs):
    print(f'>Agile.__init__(agile={agile}, args={args}, kwargs={kwargs})')
    super().__init__(*args, **kwargs)
    self.agile = agile     
    print(f'<Agile.__init__: agile={agile}')

(您甚至可以编写一个自动执行此操作的装饰器,使用一点inspect 魔法。)

如果你这样做,它会打印出如下内容:

> Agile.__init__(agile=True, args=(), kwargs={'name': 'Parker', 'sneaky':False})
> Sneaky.__init__(sneaky=False, args=(), kwargs={'name': 'Parker'})
> Character.__init__(name='Parker', args=(), kwargs={})
< Character.__init__: name: 'Parker'
< Sneaky.__init__: sneaky: False
< Agile.__init__: agile: True

所以,您对通过super 调用事物的顺序是正确的,而堆栈在返回途中弹出的顺序显然是完全相反的。


但是,与此同时,你搞错了一个细节:

发送到 Sneaky 类(通过 super),同样的事情会发生 - 所有参数都被解包,与 Sneaky 参数交叉引用(这是在设置 Sneaky = False 时)

这是参数/局部变量 sneaky 设置的位置,但 self.sneaky 直到 super 返回后才会设置。在此之前(包括在Character.__init__ 期间,同样对于您选择在Sneaky 之后添加的任何其他混入),self.__dict__ 中没有sneaky,所以如果有人要尝试查找self.sneaky ,他们只能找到具有错误值的类属性。


这又提出了一点:这些类属性是干什么用的?如果您希望它们提供默认值,那么您已经在初始值设定项参数上获得了默认值,因此它们是无用的。

如果您希望它们在初始化期间提供值,那么它们可能是错误的,因此它们比无用更糟糕。如果您需要在调用Character.__init__ 之前拥有self.sneaky,方法很简单:只需将self.sneaky = sneaky 移到super() 调用之前

事实上,这是 Python 的“显式super”模型的优势之一。在某些语言中,比如 C++,无论是从内到外还是从外到内,构造函数总是被自动调用。Python 强制你显式地执行它不太方便,而且更难出错——但这意味着你可以选择在之前进行设置或者在基类获得机会之后(或者,当然,每个都有一点),这有时很有用。

【讨论】:

  • 感谢您的详细解释。我在跟随在线 python 课程的同时创建了这段代码。我不确定为什么讲师让我制作类属性并在初始化参数中声明它们。希望他能在接下来的几个步骤中解释原因。我会把这个问题转达给他们的表格,看看我能不能得到一个好的答案。您的回答中对我来说有很多新术语,所以我有一些学习要做。再次感谢。
猜你喜欢
  • 2021-02-20
  • 1970-01-01
  • 1970-01-01
  • 2014-05-17
  • 1970-01-01
  • 2014-09-23
  • 1970-01-01
  • 2021-03-29
  • 1970-01-01
相关资源
最近更新 更多