有人能帮助我理解MRO在python中是如何工作的吗?假设我有四个类-角色,小偷,敏捷,史奈基。人物是小偷的超级阶级,敏捷和斯内基是兄弟姐妹。请看下面的代码和问题。
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类(通过超级),在那里会发生相同的事情-所有的参数都会被解压缩,并与Sneaky参数交叉引用(这是当设置了鬼怪= False时),然后用kwargs打包并发送到字符。然后将运行字符inint方法中的所有内容,并设置所有值(如name = "Parker")。
我认为MRO在回来的路上是如何工作的
既然所有东西都到了字符类中,并且字符init方法中的所有东西都运行了,那么现在它必须返回到Agile和Sneaky类,并完成它们init方法中的所有操作(或者它们的超级方法下的所有东西)。因此,它将首先返回到Sneaky类并完成它的init方法,然后返回到敏捷类并完成其init方法的其余部分。
我有没有把它弄糊涂了?呼。对不起,我知道这是很多事情,但我真的被困在这里了,我正在努力弄清楚MRO是如何工作的。
谢谢大家。
发布于 2018-08-25 04:45:39
发布的代码甚至没有编译,更不用说运行了。但是,猜测它应该如何工作,…
是的,你的事情基本上是对的。
但你应该能够自己来验证,有两种方式。知道如何验证它可能比知道答案更重要。
首先,打印出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类(通过超级),在那里会发生相同的事情-所有的参数都会被解压,交叉引用到Sneaky参数(这是在设置了鬼怪= False时)。
这是设置参数/局部变量sneaky的地方,但是self.sneaky在super返回之后才会被设置。在此之前(包括在Character.__init__期间,以及您选择在Sneaky之后添加的任何其他混合器),self.__dict__中没有sneaky,因此,如果有人试图查找self.sneaky,他们将只能找到类属性--这个属性的值是错误的。
这就引出了另一点:这些类属性是用来做什么的?如果您希望它们提供默认值,那么您已经在初始化器参数上获得了默认值,因此它们是无用的。
如果您希望它们在初始化时提供值,那么它们可能是错误的,因此它们比无用的更糟糕。如果在调用self.sneaky之前需要有一个Character.__init__,那么方法很简单:只需在调用super()之前将self.sneaky = sneaky向上移动即可。
事实上,这是Python“显式super”模型的优点之一。在某些语言中,如C++,构造函数总是自动调用,无论是从内到外还是从外部调用。Python强迫您显式地执行它不太方便,也很难出错--但它意味着您可以选择在基类获得机会之前或之后(当然,每个类都有一些设置)进行设置,这有时是有用的。
https://stackoverflow.com/questions/52013570
复制相似问题