面向对象的三大特性之继承
一、什么是继承
- 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
- 子类会“”遗传”父类的属性,从而解决代码重用问题(去掉冗余的代码)
- python中类的继承分为:单继承和多继承
二、单继承与多继承
class Parent1(object):
...
class Parent2(object):
...
# 单继承,基类是Parent1,派生类是SubClass1
class SubClass1(Parent1):
...
# python支持多继承,用逗号分隔开多个继承的类
class SubClass2(Parent1, Parent2):
...
三、查看继承
# __base__只查看从左到右继承的第一个子类
print(SubClass2.__base__) # (<class '__main__.Parent1'>,)
# __bases__则是查看所有继承的父类
print(SubClass2.__bases__) # (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
四、经典类与新式类
- 只有在python2中才分新式类和经典类,python3中统一都是新式类
- 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
- 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如
__str__
)的实现。
class Parent1(object):
...
class Parent2(object):
...
print(Parent1.__bases__) # (<class 'object'>,)
print(Parent2.__bases__) # (<class 'object'>,)
五、继承与抽象
- 继承描述的是子类与父类之间的关系,是一种什么是什么的关系。
- 要找出这种关系,必须先抽象再继承
- 抽象即抽取类似或者说比较像的部分
[1]抽象
- 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
[2]继承
- 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
- 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
六、继承与重用性
[1]继承可以减少代码的重复和冗余
案例:
- 如果我们要建立几个有相同功能或者逻辑的类,如下:
# 猫会喵喵叫,会吃喝拉撒睡,而狗会汪汪叫,也会吃喝拉撒睡
#猫类:
class Cat(object):
def eat(self):
print("猫会吃东西")
def drink(self):
print("猫会喝水")
def excrete(self):
print("猫会用猫砂盆")
def sleep(self):
print("猫会睡觉")
def meow(self):
print("猫会喵喵叫")
#狗类:
class Dog(object):
def eat(self):
print("狗会吃东西")
def drink(self):
print("狗会喝水")
def excrete(self):
print("狗会用猫砂盆")
def sleep(self):
print("狗会睡觉")
def bark(self):
print('狗会汪汪叫')
- 如果使用 继承 的思想,如下实现:
- 动物:吃、喝、拉、撒
- 猫:喵喵叫(猫继承动物的功能)
- 狗:汪汪叫(狗继承动物的功能)
class Animal(object):
def eat(self):
print("会吃东西")
def drink(self):
print("会喝水")
def excrete(self):
print("会用猫砂盆")
def sleep(self):
print("会睡觉")
class Cat(Animal):
def meow(self):
print("猫会喵喵叫")
class Dog(Animal):
def bark(self):
print('狗会汪汪叫')
dog = Dog()
dog.eat() # 会吃东西
dog.bark() # 狗会汪汪叫
cat = Cat()
cat.drink() # 会喝水
cat.meow() # 猫会喵喵叫
[2]代码的重用
- 在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
- 我们不可能从头开始写一个类B,这就用到了类的继承的概念。
- 通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
七、属性查找顺序
[1]不隐藏属性
- 有了继承关系,对象在查找属性时
- 先从对象自己的
__dict__
中找 - 如果没有则去子类中找
- 再去上一级的父类里找
- 直至找到属性或者找不到报错
- 先从对象自己的
class Parent(object):
def index(self):
print('这是父类中的index')
def func(self):
print('这是父类中的func')
self.index()
class SubClass(Parent):
def index(self):
print('这是子类中的index')
#实例化对象obj
obj = SubClass()
#子类中没有func,去父类中找
obj.func()
'''
父类中的func
这是子类中的index
'''
'''
ps:为何使用的是子类中的index?
当子类去父类中寻找func是带过去的self属性是子类示例化出来的对象所以先从子类中开始找index所以最后执行的是子类中的代码
'''
[2]隐藏属性
class Parent(object):
def __index(self):
print('这是父类中的index')
def func(self):
print('这是父类中的func')
self.__index()
class SubClass(Parent):
def __index(self):
print('这是子类中的index')
obj = SubClass()
obj.func()
"""
这是父类中的func
这是父类中的index
"""
"""
因为将index改为了__index 那么在父类中执行self.__index 就等于执行 self._Parent__index,将这个方法私有了,所以就会执行得类中的代码
"""
八、继承实现的原理
[1]非菱形结构的继承顺序
- 在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)
- 如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性
[2]菱形结构的继承顺序
- 如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
(1)深度优先
- 当类是经典类时,多继承情况下,在查找属性不存在时,会按照深度优先的方式查找下去
(2)广度优先
- 当类是新式类时,多继承情况下,在查找属性不存在时,会按照广度优先的方式查找下去
(3)代码
- 使用的是Python3.10版本所以类全都是新式类,依照广度优先原则
class X(object):
def test(self):
print('这是X')
class A(X):
def test(self):
print('这是A')
class B(A):
def test(self):
print('这是B')
class C(A):
def test(self):
print('这是C')
class D(C):
def test(self):
print('这是D')
class E(X):
def test(self):
print('这是E')
class F(B, D, E):
...
f = F()
f.test()
print(F.__mro__)
# F>B>D>C>A>E>X
# (<class '__main__.F'>,
# <class '__main__.B'>,
# <class '__main__.D'>,
# <class '__main__.C'>,
# <class '__main__.A'>,
# <class '__main__.E'>,
# <class '__main__.X'>,
# <class 'object'>)
九、Mixins
机制
[1]引入
- 一个子类可以同时继承多个父类,这样的设计常被人诟病
- 一来它有可能导致可恶的菱形问题
- 二来在人的世界观里继承应该是个”is-a”关系。
- 比如轿车类之所以可以继承交通工具类,是因为基于人的世界观
- 我们可以说:轿车是一个(“is-a”)交通工具
- 而在人的世界观里,一个物品不可能是多种不同的东西,
[2]多重继承问题
- 因此多重继承在人的世界观里是说不通的
- 它仅仅只是代码层面的逻辑。
- 不过有没有这种情况,一个类的确是需要继承多个类呢?
- 答案是有,我们还是拿交通工具来举例子:
- 民航飞机、直升飞机、轿车都是一个(is-a)交通工具
- 前两者都有一个功能是飞行fly,但是轿车没有
- 所以如下所示我们把飞行功能放到交通工具这个父类中是不合理的
- 民航飞机、直升飞机、轿车都是一个(is-a)交通工具
[3]Mixins
机制详解
-
简单来说
Mixins
机制指的是子类混合(mixin
)不同类的功能 -
就是将那些放入父类会影响其他子类功能实现的或者其他子类不应该拥有的,但如果提出来再创建一个独特的父类又违背了继承最初功能(减少代码的冗余)的特定功能使用特定的命名规范进行单独表示成功能类的行为。(个人理解)
-
大白话说就是为了遵循继承的“is-a”原则,子类只能继承一个标识其归属含义的父类(儿子只能有一个父亲),而其他类都是功能类,子类只是使用其类中的功能,而不属于此类。
-
将麻烦的功能提出来重新命名并单独成类
class Vehicle(object):
def __init__(self, name):
self.name = name
# 定义方法‘可以载人’
def carry(self):
print(f'{self.name}可以载人')
# 定义功能类‘能飞’
class Flyable(object):
def __init__(self, name):
self.name = name
# 定义方法‘能飞
def fly(self):
print(f'{self.name}能飞')
# 子类’飞机‘,继承了父类‘交通工具’和功能类‘能飞’
class Plane(Vehicle, Flyable):
def __init__(self, name):
super().__init__(name)
# 子类’汽车‘,继承了父类‘交通工具’
class Car(Vehicle):
def __init__(self, name):
Vehicle.__init__(self, name)
plane_1 = Plane('波音747')
plane_1.carry()
plane_1.fly()
car_1 = Car('五菱宏光')
car_1.carry()
- 例如上面代码中的能飞的功能类,如果放入父类交通工具就会导致其他继承的子类:汽车子类就拥有了能飞的功能,不符合常理。于是将能飞这个功能单独拿出来,定义类一个功能类