Python 简明教程 --- 21,Python 继承与多态
微信公众号:码农充电站pro
个人主页:https://codeshellme.github.io
程序不是年轻的专利,但是,它属于年轻。
目录
我们已经知道封装
,继承
和多态
是面向对象的三大特征,面向对象语言都会提供这些机制。
1,封装
在这一节介绍类的私有属性和方法
的时候,我们已经讲到过封装
。
封装
就是在设计一个类的时候,只允许使用者访问他需要的方法,将复杂的,没有必要让使用者知道的方法隐藏起来。这样,使用者只需关注他需要的东西,为其屏蔽了复杂性。
私有性
就是实现封装
的一种手段,这样,类的设计者就可以控制类中的哪些属性和方法可以被使用者访问到。一般,类中的属性,和一些复杂的方法都不会暴露给使用者。
由于前边的章节介绍过封装,这里就不再举例说明了。
2,继承
通过继承
的机制,可使得子类
轻松的拥有父类
中的属性和方法
。继承
也是一种代码复用
的方式。
Python 支持类的继承,继承的类
叫做子类
或者派生类
,被继承的类
叫做父类
或基类
。
继承的语法如下:
class 子类名(父类名):
pass
在子类名
后边的括号中,写入要继承的父类。
object
类
在Python 的继承体系中,object
是最顶层类,它是所有类的父类。在定义一个类时,如果没有继承任何类,会默认继承object
类。如下两种定义方式是等价的:
# 没有显示继承任何类,默认继承 object
class A1:
pass
# 显示继承 object
class A2(object):
pass
每个类中都有一个mro
方法,该方法可以打印类的继承关系(顺序)。我们来查看A1
和 A2
的继承关系:
>>> A1.mro()
[<class '__main__.A1'>, <class 'object'>]
>>>
>>> A2.mro()
[<class '__main__.A2'>, <class 'object'>]
可见这两个类都继承了 object
类。
继承中的__init__
方法
当一个子类继承一个父类时,如果子类中没有定义__init__
,在创建子类的对象时,会调用父类的__init__
方法,如下:
#! /usr/bin/env python3
class A(object):
def __init__(self):
print('A.__init__')
class B(A):
pass
以上代码中,B
继承了A
,A
中有__init__
方法,B
中没有__init__
方法,创建类B
的对象b
:
>>> b = B()
A.__init__
可见A
中的__init__
被执行了。
方法覆盖
如果类B
中也定义了__init__
方法,那么,就只会执行B
中的__init__
方法,而不会执行A
中的__init__
方法:
#! /usr/bin/env python3
class A(object):
def __init__(self):
print('A.__init__')
class B(A):
def __init__(self):
print('B.__init__')
此时创建B
的对象b
:
>>> b = B()
B.__init__
可见,此时只执行了B
中的__init__
方法。这其实是方法覆盖
的原因,因为子类
中的__init__
与父类
中的__init__
的参数列表一样,此时,子类中的方法覆盖了父类中的方法,所以创建对象b
时,只会执行B
中的__init__
方法。
当发生继承关系(即一个子类继承一个父类)时,如果子类中的一个方法与父类中的一个方法
一模一样
(即方法名相同,参数列表也相同),这种情况就是方法覆盖
(子类中的方法会覆盖父类中的方法)。
方法重载
当方法名
与参数列表
都一样时会发生方法覆盖
;当方法名
一样,参数列表
不一样时,会发生方法重载
。
在单个类中,代码如下:
#! /usr/bin/env python3
class A(object):
def __init__(self):
print('A.__init__')
def test(self):
print('test...')
def test(self, i):
print('test... i:%s' % i)
类A
中的两个test
方法,方法名
相同,参数列表
不同。
其实这种情况在Java
和 C++
是允许的,就是方法重载
。而在Python 中,虽然在类中这样写不会报错,但实际上,下面的test(self, i)
已经把上面的test(self)
给覆盖掉了。创建出来的对象只能调用test(self, i)
,而test(self)
是不存在的。
示例:
>>> a = A() # 创建 A 的对象 a
A.__init__
>>>
>>> a.test(123) # 可以调用 test(self, i) 方法
test... i:123
>>>
>>> a.test() # 调用 test(self) 发生异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'
在继承关系中,代码如下:
#! /usr/bin/env python3
class A(object):
def __init__(self):
print('A.__init__')
def test(self):
print('test...')
class B(A):
def __init__(self):
print('B.__init__')
def test(self, i):
print('test... i:%s' % i)
上面代码中B
继承了A
,B
和 A
中都有一个名为test
的方法,但是参数列表
不同。
这种情况跟在单个类中的情况是一样的,在类B
中,test(self, i)
会覆盖A 中的test(self)
,类B
的对象只能调用test(self, i)
,而不能调用test(self)
。
示例:
>>> b = B() # 创建 B 的对象
B.__init__
>>>
>>> b.test(123) # 可以调用 test(self, i) 方法
test... i:123
>>>
>>> b.test() # 调用 test(self) 方法,出现异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'
super()
方法
super()
方法用于调用父类中的方法。
示例代码:
#! /usr/bin/env python3
class A(object):
def __init__(self):
print('A.__init__')
def test(self):
print('class_A test...')
class B(A):
def __init__(self):
print('B.__init__')
super().__init__() # 调用父类中的构造方法
def test(self, i):
print('class_B test... i:%s' % i)
super().test() # 调用父类中的 test 方法
演示:
>>> b = B() # 创建 B 的对象
B.__init__ # 调用 B 的构造方法
A.__init__ # 调用 A 的构造方法
>>>
>>> b.test(123) # 调用 B 中的 test 方法
class_B test... i:123
class_A test... # 执行 A 中的 test 方法
is-a
关系
一个子类的对象,同时也是一个父类的对象,这叫做is-a
关系。但是一个父类的对象,不一定是一个子类的对象。
这很好理解,就像,猫一定是动物,但动物不一定是猫。
我们可以使用isinstance()
函数来判断一个对象是否是一个类的实例。
比如我们有如下两个类,Cat
继承了 Animal
:
#! /usr/bin/env python3
class Animal(object):
pass
class Cat(Animal):
pass
来看下对象和类之间的从属关系:
>>> a = Animal() # 创建 Animal 的对象
>>> c = Cat() # 创建 Cat 的对象
>>>
>>> isinstance(a, Animal) # a 一定是 Animal 的实例
True
>>> isinstance(c, Cat) # c 一定是 Cat 的实例
True
>>>
>>> isinstance(c, Animal) # Cat 继承了 Animal,所以 c 也是 Animal 的实例
True
>>> isinstance(a, Cat) # 但 a 不是 Cat 的实例
False
3,多继承
多继承
就是一个子类同时继承多个父类,这样,这个子类就同时拥有了多个父类的特性。
C++ 语言中允许多继承,但由于多继承会使得类的继承关系变得复杂。因此,到了Java 中,就禁止了多继承的方式,取而代之的是,在Java 中允许同时继承多个接口
。
Python 中也允许多继承,语法如下:
# 括号中可以写多个父类
class 子类名(父类1, 父类2, ...):
pass
我们构造一个如下的继承关系:
代码如下:
#! /usr/bin/env python3
class A(object):
def test(self):
print('class_A test...')
class B(A):
def test(self):
print('class_B test...')
class C(A):
def test(self):
print('class_C test...')
class D(B, C):
pass
类A
,B
,C
中都有test()
方法,D
中没有test()
方法。
使用D
类中的mro()
方法查看继承关系:
>>> D.mro()
[<class 'Test.D'>, <class 'Test.B'>, <class 'Test.C'>, <class 'Test.A'>, <class 'object'>]
创建D
的对象:
>>> d = D()
如果类D
中有test()
方法,那么d.test()
肯定会调用D
中的test()
方法,这种情况很简单,不用多说。
当类D
中没有test()
方法时,而它继承的父类 B
和 C
中都有 test()
方法,此时会调用哪个test()
呢?
>>> d.test()
class_B test...
可以看到d.test()
调用了类B
中的 test()
方法。
实际上这种情况下,Python 解释器会根据D.mro()
的输出结果来依次查找test()
方法,即查找顺序是D->B->C->A->object
。
所以d.test()
调用了类B
中的 test()
方法。
建议:
由于
多继承
会使类的继承关系变得复杂,所以并不提倡过多的使用多继承
。
4,多态
多态
从字面上理解就是一个事物可以呈现多种状态。继承
是多态的基础。
在上面的例子中,类D
的对象d
调用test()
方法时,沿着继承链
(D.mro()
)查找合适的test()
方法的过程,就是多态的表现过程。
比如,我们有以下几个类:
Animal
:有一个speak()
方法Cat
:继承Animal
类,有自己的speak()
方法Dog
:继承Animal
类,有自己的speak()
方法Duck
:继承Animal
类,有自己的speak()
方法
Cat
,Dog
,Duck
都属于动物,因此都继承Animal
,代码如下:
#! /usr/bin/env python3
class Animal(object):
def speak(self):
print('动物会说话...')
class Cat(Animal):
def speak(self):
print('喵喵...')
class Dog(Animal):
def speak(self):
print('汪汪...')
class Duck(Animal):
def speak(self):
print('嘎嘎...')
def animal_speak(animal):
animal.speak()
我们还定义了一个animal_speak
函数,它接受一个参数animal
,在函数内,调用了speak()
方法。
实际上,这种情况下,我们调用animal_speak
函数时,可以为它传递Animal
类型的对象,以及任何的Animal
子类的对象。
传递Animal
的对象时,调用了Animal
类中的 speak()
:
>>> animal_speak(Animal())
动物会说话...
传递Cat
的对象时,调用了Cat
类中的 speak()
:
>>> animal_speak(Cat())
喵喵...
传递Dog
的对象时,调用了Dog
类中的 speak()
:
>>> animal_speak(Dog())
汪汪...
传递Duck
的对象时,调用了Duck
类中的 speak()
:
>>> animal_speak(Duck())
嘎嘎...
可以看到,我们可以给animal_speak()
函数传递多种不同类型
的对象,为animal_speak()
函数传递不同类型的参数,输出了不同的结果,这就是多态
。
5,鸭子类型
在静态类型
语言中,有严格的类型判断,上面的animal_speak()
函数的参数只能传递Animal
及其子类
的对象。
而Python 属于动态类型
语言,不会进行严格的类型判断。
因此,我们不仅可以为animal_speak()
函数传递Animal
及其子类
的对象,还可以传递其它与Animal
类毫不相关的类的对象,只要该类中有speak()
方法就行。
这种特性,在Python 中被叫做鸭子类型
,意思就是,只要一个事物走起来像鸭子,叫起来像鸭子,那么它就是鸭子,即使它不是真正的鸭子
。
从代码上来说,只要一个类中有speak()
方法,那么就可以将该类的对象传递给animal_speak()
函数。
比如,有一个鼓类Drum
,其中有一个函数speak()
:
class Drum(object):
def speak(self):
print('咚咚...')
那么,类Drum
的对象也可以传递给animal_speak()
函数,即使Drum
与Animal
类毫不相关:
>>> animal_speak(Drum())
咚咚...
从另一个角度来考虑,实际上Python 函数中的参数,并没有标明参数的类型。在animal_speak()
函数中,我们只是将参数叫做了animal
而已,因此我们就认为animal_speak()
函数应该接受Animal 类及其子类的对象,其实这仅仅只是我们认为的而已。
计算机并不知道animal
的含义,如果我们将原来的animal_speak()
函数:
def animal_speak(animal):
animal.speak()
改写成:
def animal_speak(a):
a.speak()
实际上,我们知道,这两个函数并没有任何区别。因此,参数a
可以是任意的类型,只要a
中有speak()
方法就行。这就是Python 能够表现出鸭子特性
的原因。
(完。)
推荐阅读:
Python 简明教程 --- 16,Python 高阶函数
Python 简明教程 --- 17,Python 模块与包
Python 简明教程 --- 18,Python 面向对象
Python 简明教程 --- 19,Python 类与对象
Python 简明教程 --- 20,Python 类中的属性与方法
欢迎关注作者公众号,获取更多技术干货。
Python 简明教程 --- 21,Python 继承与多态的更多相关文章
- Python 简明教程 --- 22,Python 闭包与装饰器
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. 目录 本节我们来介绍闭包与装饰器. 闭包与 ...
- Python 简明教程 --- 23,Python 异常处理
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...
- Python 简明教程 --- 24,Python 文件读写
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 过去的代码都是未经测试的代码. 目录 无论是哪种编程语言,IO 操作都是非常重要的部分.I 即Inp ...
- Python 简明教程 --- 25,Python 目录操作
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 做技术一定要一颗恒心,这样才不会半途而废. 目录 上一节我们介绍了文件相关的操作,本节我们来介绍目录 ...
- Python 简明教程 --- 26,Python 多进程编程
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 学编程最有效的方法是动手敲代码. 目录 1,什么是多进程 我们所写的Python 代码就是一个程序, ...
- Python 简明教程 --- 18,Python 面向对象
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 代码能借用就借用. -- Tom Duff 目录 编程可分为面向过程编程和面向对象编程,它们是两种不 ...
- 【笔记】Python简明教程
Python简明教程,此资源位于http://woodpecker.org.cn/abyteofpython_cn/chinese/ s=u'中文字符' #u表示unicode,使用u之后能正常显示中 ...
- Python 简明教程 --- 8,Python 字符串函数
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 好代码本身就是最好的文档.当你需要添加一个注释时,你应该考虑如何修改代码才能不需要注释. -- St ...
- Python 简明教程 --- 5,Python 表达式与运算符
微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 靠代码行数来衡量开发进度,就像是凭重量来衡量飞机制造的进度. -- Bill Gates 目录 1, ...
随机推荐
- Java实现 LeetCode 77 组合
77. 组合 给定两个整数 n 和 k,返回 1 - n 中所有可能的 k 个数的组合. 示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], ...
- Android中如何使用列表对话框
给按钮绑定,并且设置Click事件 bt3=findViewById(R.id.btn3); bt3.setOnClickListener(new View.OnClickListener() { @ ...
- Java实现 蓝桥杯 算法提高最小方差生成树
1 问题描述 给定带权无向图,求出一颗方差最小的生成树. 输入格式 输入多组测试数据.第一行为N,M,依次是点数和边数.接下来M行,每行三个整数U,V,W,代表连接U,V的边,和权值W.保证图连通.n ...
- java实现第六届蓝桥杯垒骰子
垒骰子 题目描述 赌圣atm晚年迷恋上了垒骰子,就是把骰子一个垒在另一个上边,不能歪歪扭扭,要垒成方柱体. 经过长期观察,atm 发现了稳定骰子的奥秘:有些数字的面贴着会互相排斥! 我们先来规范一下骰 ...
- Python—变量,条件语句,while循环,运算符,字符串等
Python初识以及变量: 变量名:——字母 ——数字 ——下划线[见名识意] (PS:数字不能开头:不能是关键字:最好不能和python内置的东西重复) ##################### ...
- zabbix 磁盘分区监控
系统环境 Zabbix 版本:3.4 操作系统版本:centos7.4 监控分区 / ./boot ./home 先创建监控项的模板 点击创建监控项 名称随意填写,键值的话因为我们监控车的是磁盘剩 ...
- 【大厂面试06期】谈一谈你对Redis持久化的理解?
Redis持久化是面试中经常会问到的问题,这里主要通过对以下几个问题进行分析,帮助大家了解Redis持久化的实现原理. 1.Redis持久化是什么? 2.Redis持久化有哪些策略?各自的实现原理是怎 ...
- 关于前端JS走马灯(marquee)总结
方案一: <marquee width="360" scrolldelay="20" scrollamount="2" onclick ...
- 菜渣开源一个基于 EMIT 的 AOP 库(.NET Core)
目录 1,快速入门 1.1 继承 ActionAttribute 特性 1.2 标记代理类型 2,如何创建代理类型 2.1 通过API直接创建 2,创建代理类型 通过API 通过 Microsoft. ...
- liunx 常用快捷键
1.命令行快捷键ctrl + a //把光标移动到最前面ctrl + e //把光标移动到最后面ctrl + l //清屏ctrl + c //取消ctrl + u //把光标到行首的删除ctrl + ...