Files
Cloud-book/Python/Python面向对象/继承与多态.md
2025-08-27 17:10:05 +08:00

790 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 继承
子类可复用父类的属性和方法,并扩展新功能,实现代码复用和逻辑分层。例如,"电动车"类继承自"汽车"类,新增"充电"方法。
不用继承创建对象
```python
class Person:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
```
使用继承的方式
```python
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class Dog(Aniaml):
pass
xiaotianquan = Dog("哮天犬",5)
xiaotianquan.eat()
```
继承概念:子类拥有父类的所有方法和属性。
<img src="继承与多态/继承对比图示.png" alt="img-继承对比图示" style="zoom:80%;" />
**继承的优点**
1. 增加了类的耦合性(耦合性不宜多,宜精)。
2. 减少了重复代码。
3. 使得代码更加规范化,合理化。
# 继承分类
上面的那个例子,涉及到的专业术语:
- `Dog` 类是 `Animal` 类的**子类**`Animal` 类是 `Dog` 类的**父类**`Dog` 类从 `Animal` 类**继承**
- `Dog` 类是 `Animal` 类的**派生类**`Animal` 类是 `Dog` 类的**基类**`Dog` 类从 `Animal` 类**派生**
继承:可以分**单继承,多继承**。
这里需要补充一下 python 中类的种类(继承需要):
在 python 2 版本中存在两种类.
- ⼀个叫**经典类**. 在 Python2 中,经典类是指没有显式继承自 `object` 的类。它们使用旧的类定义方式。
- ⼀个叫**新式类**. 新式类是指显式继承自 `object` 或其他新式类的类。新式类在 Python 2.2 中引入,并在 Python 3 中成为默认。
# 单继承
## 对象执行父类方法
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃',self)
class Person(Aniaml):
pass
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
print(Person.type_name)
Person.eat('东西')
print(Person.type_name)
p1 = Person('aaron','男',18)
print(p1.__dict__)
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()
```
## 执行顺序
```python
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class person(Aniaml):
def eat(self):
print('%s 用筷子吃饭' % self.name)
class Dog(Aniaml):
pass
class Cat(Aniaml):
pass
person1 = person('张三',18)
person1.eat()
```
## 同时执行类以及父类方法
方法一:如果想执行父类的 eat() 方法,可以在子类的方法中写上:父类.eat(对象,其他参数)
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
Aniaml.__init__(self,name,sex,age)
self.mind = mind
def eat(self):
Aniaml.eat(self)
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('aaron','男',18,'想吃东西')
p1.eat()
```
方法二:利用 `super().func(参数)`
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
# super(Person,self).__init__(name,sex,age)
super().__init__(name,sex,age)
self.mind = mind
def eat(self):
super().eat()
print('%s 吃饭' % self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('aaron','男',18,'想吃东西')
p1.eat()
```
## 方法重写
- 如果在开发中,父类的方法实现和子类的方法实现,完全不同,可以使用覆盖的方式,在子类中重新编写父类的方法实现
- 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
- 重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
## 子类中扩展父类方法
- 如果在开发中,子类的方法实现中包含父类的方法实现
- 父类原本封装的方法实现** 是 **子类方法的一部分**
- 就可以使用扩展的方式
1. 在子类中重写父类的方法
2. 在需要的位置使用 `super().父类方法` 来调用父类方法的执行
3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现
**关于 `super`**
-`Python``super` 是一个特殊的类
- `super()` 就是使用 `super` 类创建出来的对象
- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
## 父类的私有属性和私有方法
子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法,但可以通过父类的公有方法间接访问
- **私有属性、方法** 是对象的隐私,不对外公开,外界以及子类都不能直接访问
- **私有属性、方法** 通常用于做一些内部的事情
![img-父类的私有属性和私有方法](继承与多态/父类的私有属性和私有方法.png)
- `B` 的对象不能直接访问 `__num2` 属性
- `B` 的对象不能在 `demo` 方法内访问 `__num2` 属性
- `B` 的对象可以在 `demo` 方法内,调用父类的 `test` 方法
- 父类的 `test` 方法内部,能够访问 `__num2` 属性和 `__test` 方法
```python
class Animal:
def __init__(self,name):
self.__name = name
def __eat(self):
print(self.__name + "Eating...")
def eat2(self):
self.__eat()
class Dog(Animal):
pass
a = Dog('哮天犬')
print(a.name)
a.__eat()
a.eat2()
```
# 多继承
**概念**
- **子类** 可以拥有 **多个父类**,并且具有 **所有父类****属性****方法**
- 例如:**孩子** 会继承自己 **父亲****母亲****特性**
![img-多继承1](继承与多态/多继承1.png)
**语法**
```python
class 子类名(父类名1, 父类名2...)
pass
```
**问题的提出**
- 如果 **不同的父类** 中存在 **同名的方法****子类对象** 在调用方法时,会调用 **哪一个父类中**的方法呢?
> 提示:**开发时,应该尽量避免这种容易产生混淆的情况!** —— 如果 **父类之间** 存在 **同名的属性或者方法**,应该 **尽量避免** 使用多继承
![img-多继承2](继承与多态/多继承2.png)
```python
class shengxian: # 神仙
def fei(self):
print("神仙会飞")
def eat(self):
print("吃人参果")
class monkey: # 猴
def eat(self):
print("吃桃子")
class songwukong(shengxian,monkey): #孙悟空既是神仙也是猴
def __init__(self):
self.name = "孙悟空"
def eat(self):
print("我是齐天大圣,我不用吃东西")
swk = songwukong()
swk.eat()
```
## 经典类
```python
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E:
pass
class F(D, E):
pass
class G(F, D):
pass
class H:
pass
class Foo(H, G):
pass
```
示意图
![img-经典类多继承](继承与多态/经典类多继承.png)
在经典类中采⽤的是深度优先,遍历⽅案:优先遍历继承链的最左侧分支直至顶端,再向右查找其他分支。
类的 MRO (method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C。
## 新式类
C3 算法是 Python 中用于解决多继承场景下方法解析顺序MRO的核心算法其设计目标是保证继承关系的一致性本地优先级单调性
### 算法核心原理
C3 算法的核心是​**​线性化合并规则**通过递归合并父类的MRO列表生成一个无冲突的继承顺序链。
对于类 `C(B1, B2, ..., Bn)` ,其 MRO 计算公式为为:`L[C] = C + merge(L[B1],L[B2],..,L[Bn],[B1,B2,...,Bn])`
其中 `megre` 操作负责合并父类的 MRO 序列。
### `merge` 合并规则
`merge` 操作是 C3 算法的核心步骤,具体执行流程如下:
1. **选取第一个列表的头元素Head** 即列表的第一个元素,记为 `h`
2. **​检查 `h` 的有效性:**
-`h` 不在其他列表的​​尾部​​(即其他列表除首元素外的部分),则将其加入结果序列,并从所有列表中删除 `h`
-`h` 存在于其他列表的尾部,则跳过当前列表,选择下一个列表的头元素重复检查。
3. **​递归执行​​:** 重复步骤1-2直到所有列表为空成功或无法找到有效头元素失败并抛出 `TypeError`
**示例解析**
假设类 `D` 继承自 `B``C`,其父类的 MRO 分别为 `L(B)=[B, A, O]``L(C)=[C, A, O]`,则 `D` 的 MRO 计算如下:
`L(D) = D + merge([B,A,O],[C,A,O],[B,C])`
1. 初始合并列表为 `[[B, A, O], [C, A, O], B, C]`
2. 提取 `B`(不在其他列表尾部),结果序列为 `[D, B]`,剩余列表 `[[A, O], [C, A, O], C]`
3. 提取 `C`(不在其他列表尾部),结果序列扩展为 `[D, B, C]`,剩余列表 `[[A, O], [A, O]]`
4. 合并 `A``O`,最终得到 `L(D)=[D, B, C, A, O]`
### 实践案例
![img-新式类megre](继承与多态/新式类megre.png)
计算 mro(A) 方式:
step1:
L(A) = A + merge(L[B] + L[C], [B, C])
L(B) = B + merge(L[D] + L[E], [D, E])
L(C) = C + merge(L[E] + L[F], [E, F])
L(D) = D + merge(L[O]) = [D, O]
L(E) = E + merge(L[O]) = [E, O]
L[F] = F + merge(L[O]) = [F, O]
step2:
L(B) = B + merge([D, O], [E, O], [D, E])
= [B, D] + merge([O], [E, O], [E]) # 拿出并删除D
= [B, D, E] + merge([O], [O]) # 拿出并删除E
= [B, D, E, O]
L(C) = C + merge([E, O], [F, O], [E, F])
...
= [C, E, F, O]
step3:
L(A) = A + merge([B, D, E, O], [C, E, F, O], [B, C])
= [A, B] + merge([D, E, O], [C, E, F, O], [C]) # h = B 拿出并删除B
= [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = D 拿出并删除D
= [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = E 存在于其他列表的尾部则跳过
...
= [A, B, D, C, E, F, O]
**代码**
```python
class D:
pass
class E:
pass
class F:
pass
class B(D,E):
pass
class C(E,F):
pass
class A(B,C):
pass
print(A.__mro__)
```
# 多态
同一方法在不同对象中呈现不同行为,增强代码灵活性。例如,"动物"类的"发声"方法在"狗"和"猫"对象中分别输出"汪汪"和"喵喵"。
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响到类的内部设计
![img-多态示意图](继承与多态/多态示意图.png)
```python
class human(object):
def work(self):
return "喝杯咖啡,开始工作"
class ps_job(human):
def work(self):
return "开始美工"
class IT_job(human):
def work(self):
return "开始敲代码"
def job(person): # 多态函数
print(person.work())
# 创建不同类型的对象
ps = ps_job()
it = IT_job()
# 调用同一个函数,表现出不同的行为
job(ps)
job(it)
```
**案例:哮天犬**
**需求**
1.`Dog` 类中封装方法 `game`
2. 定义 `XiaoTianDog` 继承自 `Dog` ,并且重写 `game` 方法
3. 定义 `Person` 类,并且封装一个和狗玩的方法
![img-多态](继承与多态/多态.png)
**实现**
```python
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianDog(Dog):
def game(self):
print("%s 飞到天上去玩耍..." % self.name)
class Person(object):
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s%s 快乐的玩耍..." % (self.name, dog.name))
# 让狗玩耍
dog.game()
# 1. 创建一个狗对象
wangcai = Dog("旺财")
xiaotianquan = XiaoTianDog("飞天旺财")
# 2. 创建一个小明对象
xiaoming = Person("小明")
# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
xiaoming.game_with_dog(xiaotianquan)
```
**案例小结**
- `Person` 类中只需要让狗对象调用 `game` 方法,而不关心具体是什么狗。
- `game` 方法是在 `Dog` 父类中定义的。
- 在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果。
# 鸭子类型
python 中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
这句谚语是关于鸭子类型Duck Typing的一种表达方式。鸭子类型是一种动态类型的概念它强调一个对象的特征和行为而不是其具体的类型或继承关系。
在 Python 中,鸭子类型的概念可以简单地表述为:如果一个对象具有像鸭子一样的特征和行为,那么我们可以认为它是一个鸭子。这意味着我们关注对象是否具备特定的方法和属性,而不关心对象的具体类型。
这种思想在 Python 中经常被使用,特别是在函数参数传递和对象的使用上。如果一个函数接受一个参数,并假设该参数具有某些特定的方法或属性,那么只要传递的对象满足这些要求,它就可以正常工作,无论对象的具体类型是什么。
下面是一个简单的示例来说明鸭子类型的概念:
```python
class Duck:
def quack(self):
print("嘎嘎叫!")
def fly(self):
print("扑哧扑哧的飞!")
class Person:
def quack(self):
print("我喜欢跟鸭子一样嘎嘎叫")
def fly(self):
print("我也喜欢跟鸭子一样飞")
def make_it_quack_and_fly(obj):
obj.quack()
obj.fly()
duck = Duck()
person = Person()
make_it_quack_and_fly(duck)
make_it_quack_and_fly(person)
```
在上述示例中,我们定义了一个 `Duck` 类和一个 `Person` 类,它们都具有 `quack``fly` 方法。然后,我们定义了一个函数 `make_it_quack_and_fly`,它接受一个参数 `obj`,并调用 `obj``quack``fly` 方法。
我们可以看到,无论是 `Duck` 对象还是 `Person` 对象,只要它们具有 `quack``fly` 方法,都可以作为参数传递给 `make_it_quack_and_fly` 函数,并成功执行相应的方法。
这正是鸭子类型的思想:如果一个对象具有像鸭子一样的特征和行为(即具有 `quack``fly` 方法),那么我们可以将其视为鸭子,而无需关心对象的具体类型。
# 类的约束
写一个支付功能
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(200)
```
统一一下付款方式
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
```
如果后期添加微信支付,但是没有统一标准,换个程序员就可能写成这样。
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay:
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
print("===============")
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)
```
所以此时我们要用到对类的约束,对类的约束有两种:
1. 提取父类,然后在父类中定义好⽅法,在这个方法中什么都不⽤⼲,就抛⼀个异常就可以了,这样所有的⼦类都必须重写这个⽅法,否则,访问的时候就会报错。
2. 使⽤元类来描述方类,在元类中给出⼀个抽象方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果。(推荐)
**方法1**
```python
class Payment:
"""
此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
"""
def pay(self,money):
raise Exception("你没有实现pay方法")
class QQpay(Payment):
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay(Payment):
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)
```
**方法2引入抽象类的概念处理
```python
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass 指定的是一个元类
@abstractmethod
def pay(self):pass # 抽象方法
class Alipay(Payment):
def pay(self,money):
print('使用支付宝支付了%s元'%money)
class QQpay(Payment):
def pay(self,money):
print('使用qq支付了%s元'%money)
class Wechatpay(Payment):
# def pay(self,money):
# print('使用微信支付了%s元'%money)
def recharge(self):pass
def pay(a,money):
a.pay(money)
a = Alipay()
a.pay(100)
pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100) # 到用的时候才会报错
# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范
```
# `super()` 深入了解
super 是严格按照类的继承顺序执行。
**示例1**
```python
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class Foo(A):
def f1(self):
super().f2()
print('in A Foo')
obj = Foo()
obj.f1()
```
**示例2**
```python
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super().f1()
print('in Info f1')
obj = Info()
obj.f1()
# super() 是严格按照当前类的继承顺序执行的,不会收到过程中其他类的影响
print(Info.mro())
```
**示例3**
```python
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super(Foo,self).f1() # 这里的意思是绕过Foo从Foo的位置开始寻找下一个
print('in Info f1')
obj = Info()
obj.f1()
```