首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何MRO,超级在python工作

如何MRO,超级在python工作
EN

Stack Overflow用户
提问于 2018-08-25 02:37:01
回答 1查看 400关注 0票数 1

有人能帮助我理解MRO在python中是如何工作的吗?假设我有四个类-角色,小偷,敏捷,史奈基。人物是小偷的超级阶级,敏捷和斯内基是兄弟姐妹。请看下面的代码和问题。

代码语言:javascript
复制
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是如何工作的。

谢谢大家。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-08-25 04:45:39

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

是的,你的事情基本上是对的。

但你应该能够自己来验证,有两种方式。知道如何验证它可能比知道答案更重要。

首先,打印出Thief.mro()。它应该是这样的:

代码语言:javascript
复制
[Thief, Agile, Sneaky, Character, object]

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

代码语言:javascript
复制
>>> [cls for cls in Thief.mro() if '__init__' in cls.__dict__]
[Agile, Sneaky, Character, object]

而且,为了确保Agile确实被第一次调用:

代码语言:javascript
复制
>>> Thief.__init__
<function Agile.__init__>

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

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

代码语言:javascript
复制
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魔力。)

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

代码语言:javascript
复制
> 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.sneakysuper返回之后才会被设置。在此之前(包括在Character.__init__期间,以及您选择在Sneaky之后添加的任何其他混合器),self.__dict__中没有sneaky,因此,如果有人试图查找self.sneaky,他们将只能找到类属性--这个属性的值是错误的。

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

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

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

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52013570

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档