亲宝软件园·资讯

展开

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 属性,都会直接 跑 到子类里面。

加载全部内容

相关教程
猜你喜欢
用户评论