我在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
继承自Integer
和Positive
,并且两个类都定义了__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)
没有效果,离开人。年龄没有。
Positive
和Integer
的验证逻辑都运行,因为Type
和Positive
在__set__
中都有这一行:
super().__set__(instance, val)
这不会跳到Descriptor.__set__
。相反,它按方法解析顺序调用下一个方法。Type.__set__
被调用,它的super().__set__(instance, val)
调用Positive.__set__
。Positive.__set__
运行它的验证并调用Descriptor.__set__
,后者进行设置。这个行为是我们有super
的原因之一。
如果你想让你的test
方法像那样工作,你需要做两件事。首先,您需要使A
和B
从一个不做任何事情的test
方法的公共基类继承,因此super
链以test
方法结束,而不是转到object
:
class Base:
def test():
pass
然后,您需要将super().test()
添加到A.test
和B.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
,最终将设置属性的值。