Python 多继承中的一个诡异现象
itskingname 人气:0我们知道,在面向对象编程里面, 继承 是一个很重要的概念。子类可以使用父类的方法和属性。
例如下面这段代码:
class Father: def __init__(self): self.address = '上海' def say(self): print('我是爸爸') class Son(Father): def __init__(self): super().__init__() def say(self): print('我是儿子') son = Son() print(son.address)
运行效果如下图所示:
从图中可以看到,子类并没有 self.address
这个属性,但是当我们直接打印的时候,并不会报错,它会自动使用父类的 address
属性。
显然,如果一个属性,子类也没有,父类也没有,那肯定会报错,如下图所示:
我们也知道,Python
是支持多继承的,一个子类可以有多个父类。那么,大家请看下面这段代码:
class GrandFather: def __init__(self): self.address = '上海' def say(self): print('我是爸爸') class Father: def __init__(self): self.age = 100 def where(self): print('我现在住在:', self.address) class Son(GrandFather, Father): def __init__(self): super().__init__() def say(self): print('我是儿子') son = Son() son.where()
运行效果如下图所示:
大家仔细观察,会发现这段代码有点奇怪。我调用的是 son.where()
方法,由于 Son 类没有这个方法,于是它会去它的两个父类里面找。于是在 Father 这个父类里面找到了。于是执行 Father 里面的 where()
方法,目前为止没有问题。
但接下来就不对了, .where()
方法里面,调用了 self.address
属性。可问题是 Father
这个类它并没有 .address 属性啊!而且 Father
也没有父类,那么这个 .address
属性是从哪里来的?
难道说,在开发者不知道的隐秘的角落里面, GrandFather
类悄悄成为了 Father
的父类?这样一来, GrandFather
岂不是又是 C 的父类,又是 C 的父类的父类? GrandFather
既是爸爸又是爷爷?
实际上,并不存在这么混乱的关系。要解释这个现象,我们就要从 self
这个东西说起。
我们知道,类的属性都是以 self
开头,方法的第一个参数也是 self
。那么这个 self 到底是什么东西?我们用一段小代码来看看它是什么东西:
class A: def get_self(self): return self test = A() what_is_self = test.get_self() test is what_is_self
运行效果如下图所示:
从图里面可以看到, self
实际上就是这个类的实例。我们再来看有继承的情况:
class A: def get_self(self): return self class B(A): def __init__(self): ... test = B() what_is_self = test.get_self() print(what_is_self)
从图中可以看到,虽然我在 A 类的 .get_self()
方法中返回了 self
,但这个 self
实际上是 B 类的实例。因为我自始至终就只初始化了 B 类,并没有初始化 A 类。A 虽然是 B 类的父类。但父类的 self
都会变成子类的实例。
明白这一点以后,前面的问题就很好解释了,我们多打印一些信息:
大家注意画红线的地方, self
始终都是 Son 类的实例。所以,一开始初始化 .address
的时候,就是初始化的 Son 的实例的 .address
属性。后面在 .where
里面调用 .address
的时候,也是读取的 Son
的实例的 .address
属性。所以,并不存在 Father
类去读 GrandFather
类的情况。自始至终,都是 Son 类的实例在进行各种操作。
所以,在这个例子里面,当使用了继承以后,所有父类的属性和方法,子类如果有相同的名字,那么以子类的为准。如果子类没有定义,那么父类的属性和方法,其实都会跑到子类里面去。 所有看起来是父类进行的操作,其实都是子类在进行 。上面的代码,甚至可以近似等价于:
由于 say
方法在子类中有了定义,所以子类覆盖父类。以子类的 say 方法为准。 where
和 address
由于子类没有定义,所以 Father
类的 where
方法和 GrandFather
里面的 address
属性,都会直接 跑 到子类里面。
加载全部内容