多重继承如何在描述符中起作用?

时间:2014-05-25 11:55:54

标签: python python-3.x

我在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,表示当名称冲突时,第一个基类获胜。

所以我想知道为什么两种验证都有效?更奇怪的是,当只有整数检查通过时(例如person.age = -3),它​​的super().__set__(instance, val)无效,让person.age保持不变。

2 个答案:

答案 0 :(得分:2)

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

super().__set__(instance, val)

这不会跳到Descriptor.__set__。相反,它调用method resolution order中的下一个方法。 Type.__set__被调用,super().__set__(instance, val)调用Positive.__set__Positive.__set__运行其验证并调用Descriptor.__set__进行设置。这种行为是我们super

的原因之一

如果您希望test方法表现得那样,您需要做两件事。首先,您需要使用A方法从公共基类继承Btest,但不会执行任何操作,因此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's super() considered super

答案 1 :(得分:0)

抱歉,我的不好。

在我暂停并提出这个问题的几分钟后,视频给出了完美的解释。

因此,当多重继承发生时,每个类中定义的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列表中找到接下来的内容。

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