有人可以帮助我了解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)
所以,这就是我想发生的事情,如果我理解正确,请告诉我。
由于敏捷是列表中的第一位,因此所有参数都首先发送到Agile,在此该参数将与Agile参数进行交叉引用。如果存在匹配项,则将分配值,然后将所有不具有匹配关键字的内容打包为* kwargs并发送到Sneaky类(通过super),在此处发生相同的事情-所有参数都将解压缩,与Sneaky参数进行交叉引用(这是在设置溜溜= False时),然后打包成kwargs并发送给Character。然后,Character inint方法中的所有内容都将运行并设置所有值(如名称=“ Parker”)。
我如何思考MRO的工作原理
现在所有内容都进入了Character类,并且Character init方法中的所有内容都已运行,现在它必须返回到Agile和Sneaky类,并完成其init方法中的所有内容(或其super下的所有内容)的运行。因此,它将首先返回Sneaky类并完成其init方法,然后返回Agile类并分别完成其init方法的其余部分。
我在任何地方都感到困惑吗? ew很抱歉,我知道很多,但是我真的很困在这里,我想清楚地了解MRO的工作原理。
谢谢大家。
答案 0 :(得分:2)
您发布的代码甚至无法编译,运行更少。但是,猜测它应该如何工作……
是的,您基本上没事。
但是您应该可以通过两种方式自己进行验证。而且知道如何验证它可能比知道答案甚至更重要。
首先,只需打印出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参数进行交叉引用(这是在设置了slippey = False时)
这是设置参数/局部变量sneaky
的地方,但是self.sneaky
直到{em}之后的{em> 之后才被设置。在此之前(包括在super
期间,以及类似地,对于您选择在Character.__init__
之后插入的任何其他mixin),Sneaky
中没有sneaky
,因此如果有人要尝试查找self.__dict__
,他们只能找到具有错误值的class属性。
这又提出了一点:这些类属性是做什么用的?如果您希望它们提供默认值,那么您已经在初始化程序参数上获得了默认值,因此它们没有用。
如果您希望它们在初始化期间提供值,那么它们可能是错误的,因此比没用还糟。如果您需要在致电self.sneaky
之前拥有self.sneaky
,那么这样做的方法很简单:只需在Character.__init__
呼叫之前 上移self.sneaky = sneaky
实际上,这是Python的“显式super()
”模型的优势之一。在某些语言中,例如C ++,总是从内到外或从内到外始终自动调用构造函数。Python强制您显式执行构造函数不太方便,而且更容易出错,但这意味着您可以选择先进行设置或在基类获得机会之后(当然也有机会),这有时很有用。