暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

​既是爸爸又是爷爷?Python 多继承中的一个诡异现象

未闻Code 2021-09-17
684

摄影:产品经理   

真材实料的什么汤

我们知道,在面向对象编程里面,继承
是一个很重要的概念。子类可以使用父类的方法和属性。例如下面这段代码:

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

END

未闻 Code·知识星球开放啦!

一对一答疑爬虫相关问题

职业生涯咨询

面试经验分享

每周直播分享

......

未闻 Code·知识星球期待与你相见~

一二线大厂在职员工

十多年码龄的编程老鸟

国内外高校在读学生

中小学刚刚入门的新人

“未闻 Code技术交流群”等你来!

入群方式:添“mekingname”,备注“粉丝群”(谢绝广告党,非诚勿扰!)

文章转载自未闻Code,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论