多重继承如何在描述符中工作



我在YouTube上看了一个关于Python元编程的很棒的视频。我尝试编写以下代码(这与视频几乎相同):

class Descriptor:
    def __init__(self, name):
        self.name = name
    def __get__(self, instance, cls):
        return instance.__dict__[self.name]
    def __set__(self, instance, val):
        instance.__dict__[self.name] = val
    def __delete__(self, instance):
        del instance.__dict__[self.name]
class Type(Descriptor):
    ty = object
    def __set__(self, instance, val):
        if not isinstance(val, self.ty):
            raise TypeError("%s should be of type %s" % (self.name, self.ty))
        super().__set__(instance, val)
class String(Type):
    ty = str
class Integer(Type):
    ty = int
class Positive(Descriptor):
    def __set__(self, instance, val):
        if val <= 0:
            raise ValueError("Must be > 0")
        super().__set__(instance, val)
class PositiveInteger(Integer, Positive):
    pass
class Person(metaclass=StructMeta):
    _fields = ['name', 'gender', 'age']
    name = String('name')
    gender = String('gender')
    age = PositiveInteger('age')

说明PositiveInteger继承自IntegerPositive,并且两个类都定义了__get__方法来进行验证。我写了一些测试代码来说服自己两个方法都能运行:

class A:
    def test(self):
        self.a = 'OK'
class B:
    def test(self):
        self.b = 'OK'
class C(A, B):
    pass
c = C()
c.test()
print(self.a)
print(self.b)

只发现只有第一个print语句有效。第二个将引发AttributeError,这表明当存在名称冲突时,第一个基类获胜。

所以我想知道为什么两个验证工作?更奇怪的是,当只有Integer检查通过时(例如person。年龄= -3),这是super().__set__(instance, val)没有效果,离开人。年龄没有。

PositiveInteger的验证逻辑都运行,因为TypePositive__set__中都有这一行:

super().__set__(instance, val)

这不会跳到Descriptor.__set__。相反,它按方法解析顺序调用下一个方法。Type.__set__被调用,它的super().__set__(instance, val)调用Positive.__set__Positive.__set__运行它的验证并调用Descriptor.__set__,后者进行设置。这个行为是我们有super的原因之一。

如果你想让你的test方法像那样工作,你需要做两件事。首先,您需要使AB从一个不做任何事情的test方法的公共基类继承,因此super链以test方法结束,而不是转到object:

class Base:
    def test():
        pass
然后,您需要将super().test()添加到A.testB.test:
class A(Base):
    def test(self):
        self.a = 'OK'
        super().test()
class B(Base):
    def test(self):
        self.b = 'OK'
        super().test()

更多信息,请参见Python的super()被认为是super。

对不起,我的错。

视频在我停下来问这个问题几分钟后就给出了完美的解释。

因此,当发生多重继承时,在每个类中定义了MRO(方法解析顺序),它决定了super()链中方法的解析顺序。顺序由深度优先搜索确定,例如

class A:
    pass
class B(A):
    pass
class C(B):
    pass
class D(A):
    pass
class E(C, D):
    pass

E.__mro__将是:

(<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)

需要注意的一点是,A将在继承树中出现多次,并且在MRO列表中,它只会出现在所有A出现的最后一个位置。

这里有一个技巧:对super()的调用不一定会到它的基。相反,它会在MRO列表中找到接下来的内容。

解释一下代码中发生了什么:Integer.__get__(继承自Type.__get__)中的super()调用不会转到Descriptor.__get__,因为Descriptor在MRO列表中最后出现。它将落入Positive.__set__,然后它的super()将落入Descriptor,最终将设置属性的值。

最新更新