有人可以帮助我了解MRO如何在python中工作吗?
假设我有四个班级-角色,小偷,敏捷,鬼S。性格是盗贼的超类,敏捷和偷偷摸摸的是兄弟姐妹。请在下面查看我的代码和问题

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方法中的所有内容都会运行,并且所有值都将被设置(例如名称=“ Parker”)。

我如何思考MRO的返航方式

既然一切都进入了Character类,并且Character init方法中的所有内容都已运行,那么现在必须回到Agile和Sneaky类,并完成其init方法中的所有内容(或super之下的所有内容)的运行。因此,它将首先返回Sneaky类并完成其init方法,然后返回Agile类并完成其init方法的其余部分(分别)。

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

谢谢大家。

最佳答案

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

是的,您基本上没事。

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



首先,只需打印出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参数进行交叉引用(这是在设置便秘= False的情况下)


这是设置参数/局部变量sneaky的地方,但是self.sneaky直到super返回后才被设置。在此之前(包括在Character.__init__期间,以及类似地,对于您选择在Sneaky之后插入的任何其他mixin),在sneaky中没有self.__dict__,因此,如果有人尝试查找self.sneaky ,他们只能找到具有错误值的class属性。



这就引出了另一点:这些类的属性是什么?如果您希望它们提供默认值,那么您已经在初始化程序参数上获得了默认值,因此它们没有用。

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

实际上,这是Python的“显式super”模型的优势之一。在某些语言中,例如C ++,总是从内到外或从内到外始终自动调用构造函数。Python强制您显式执行构造函数不太方便,而且更容易出错,但这意味着您可以选择先进行设置或在基类获得机会之后(当然也有机会),这有时很有用。

10-04 14:00