有人可以帮助我了解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强制您显式执行构造函数不太方便,而且更容易出错,但这意味着您可以选择先进行设置或在基类获得机会之后(当然也有机会),这有时很有用。