Python 面向对象3大特征之继承
继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。
也就是说,通过使用继承这种机制,可以轻松实现类的重复使用,相同的代码不需要重复的编写,提高开发的效率。
一、单继承
继承的概念:子类拥有父类的所有方法和属性。
不使用继承分析:
Animal 中有4个方法,Dog 类中有5个方法,Dog 类中的4个方法可以直接从 Animal 中复制粘贴过来,Dog 类其实只需要单独开发 bark 方法。
XiaoTianQuan 类具有普通 Dog 的行为,6个方法中的前5个方法和 Dog 类的是重复的,可以直接复制过来,只需要单独开发 fly 方法。
假设我们在开发的过程中,Animal 中 喝水的方法需要修改,那就意味着 Dog 和 XiaoTianQuan 中的喝水方法也要跟着修改,这样就显得太麻烦了。
接下来,我们来使用继承的方式。
1、继承的语法
class 类名(父类名):
...
...
子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发。
子类中应该根据职责,封装子类特有的属性和方法。
实例:
class Animal:
def eat(self):
print("吃")
def drink(self):
print("喝")
def run(self):
print("跑")
def sleep(self):
print("睡")
class Dog(Animal):
# 子类拥有父类的所有属性和方法
def bark(self):
print("汪汪叫")
# 创建一个对象 - 狗对象
wangcai = Dog()
wangcai.eat() # 吃
wangcai.drink() # 喝
wangcai.run() # 跑
wangcai.sleep() # 睡
wangcai.bark() # 汪汪叫
在对于继承的概念上可能存在不一样的描述,不过都是指同一件事情。比如:
Dog 类是 Animal 类的子类,Animal 类是 Dog 类的父类,Dog 类从 Animal 类继承。
Dog 类是 Animal 类的派生类,Animal 类是 Dog 类的基类,Dog 类从 Animal 类派生。
继承的传递性
C 类从 B 类继承,B 类又从 A 类继承,那么 C 类就具有 B 类和 A 类的所有属性和方法。
2、继承中方法的重写
当父类的方法实现不能满足子类需求时,可以对方法进行重写(override)。
重写父类方法有两种情况:
1) 覆盖父类的方法
如果在开发中,父类的方法实现和子类的方法实现完全不同,就可以使用覆盖的方式,在子类中重新编写父类的方法实现。
具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现。
重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法。
class Dog():
def bark(self):
print("汪汪叫")
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
def bark(self):
print("叫得跟神一样...")
xtq = XiaoTianQuan()
# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark() # 叫得跟神一样...
2) 对父类方法进行扩展
如果在开发中,子类的方法实现中包含父类的方法实现,父类原本封装的方法实现是子类方法的一部分,那么就可以使用扩展的方式。
在子类中重写父类的方法,在需要的位置使用 super().父类方法来调用父类方法的执行,在代码其他的位置针对子类的需求,编写子类特有的代码实现。
class Dog():
def bark(self):
print("汪汪叫")
class XiaoTianQuan(Dog):
def fly(self):
print("我会飞")
def bark(self):
# 1. 针对子类特有的需求,编写代码
print("神一样的叫唤...")
# 2. 使用 super(). 调用原本在父类中封装的方法
super().bark()
xtq = XiaoTianQuan()
xtq.bark()
# 神一样的叫唤...
# 汪汪叫
关于 super
在 Python 中 super 是一个特殊的类,super() 就是使用 super 类创建出来的对象,最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。
3、父类的私有属性和私有方法
子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法。
私有属性、方法通常用于做一些内部的事情,它们是对象的隐私,不对外公开,外界以及子类都不能直接访问,不过子类对象可以通过父类的公有方法间接访问到私有属性或私有方法。
示例:
根据上面的类图,得出以下结论:B 的对象不能直接访问 __num2 属性,B 的对象不能在 demo 方法内访问 __num2 属性,B 的对象可以在 demo 方法内,调用父类的 test 方法,父类的 test 方法内部,能够访问 __num2 属性和 __test 方法。
二、多继承
子类可以拥有多个父类,并且具有所有父类的属性和方法。
事实上,大部分面向对象的编程语言,都只支持单继承,即子类有且只能有一个父类。而 Python 却支持多继承。
例如:孩子会继承自己父亲和母亲的特性。
语法:
class 子类名(父类名1, 父类名2...)
pass
示例:
class A:
def test(self):
print("test 方法")
class B:
def demo(self):
print("demo 方法")
class C(A, B):
"""多继承可以让子类对象,同时具有多个父类的属性和方法"""
pass
c = C()
c.test() # test 方法
c.demo() # demo 方法
多继承的使用注意事项
如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承,以免产生混淆的情况。
但是,如果确实继承了存在同名属性和方法的父类,那么,子类对象在调用方法时,会调用哪一个父类中的方法呢?
class A:
def test(self):
print("A --- test 方法")
def demo(self):
print("A --- demo 方法")
class B:
def test(self):
print("B --- test 方法")
def demo(self):
print("B --- demo 方法")
class C(B, A):
pass
# 创建子类对象
c = C()
c.test() # B --- test 方法
c.demo() # B --- demo 方法
从效果上来看,子类调用父类的方法的顺序好像根据继承父类的顺序来的,但其实不能这么简单的判定。
Python 中的 MRO —— 方法搜索顺序
Python 中针对类提供了一个内置属性 mro 可以查看方法搜索顺序。
MRO 是 method resolution order,主要用于在多继承时判断方法、属性的调用路径。
紧接上面的例子:
# 确定C类对象调用方法的顺序
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
在搜索方法时,是按照 __mro__ 的输出结果从左至右的顺序查找的;如果在当前类中找到方法,就直接执行,不再搜索;如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索;如果找到最后一个类,还没有找到方法,程序报错。
虽然 Python 在语法上支持多继承,但逼不得已,建议大家不要使用多继承。
和单继承相比,多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
如果一个类的父类不是 object,在重写初始化方法时,一定要先 super() 一下父类的 __init__ 方法,保证父类中实现的 __init__ 代码能够被正常执行。