前面已经讲过了Python作为一门面向对象的编程语言所应有的基础知识,从现在开始重点学习OOP的三大特性:继承、封装(隐藏)和多态。
2020年5月4日11:47:54
OOP三大特性
什么是继承
继承就是可以让子类也具有父类的特性,从而达到代码复用的目的。这在设计之上就是一种增量进化:保持原父类设计的稳定性,而且可以增加新的功能 or 改进已有的算法。
所谓代码复用:B类(子类)继承与A类(父类),则B(派生类)将直接拥有A(基类)的特征。这大大降低了编程的复杂度和重复性。
下面看一下具体的语法格式: (注:Python也可以多重继承 :一个派生类可以直接继承多个基类)
class 派生类类名(基类1 [,基类2,基类3 ……]):
类的内部
……
我们前面在学习OOP的时候,所写的类 都是没有指定继承的派生类。实际上默认继承的基类为:object类。它是所有类的基类,它里面定义了大量所有类的共有的方法实现 如:__new__()方法
注:同学习C++的时候一样,在定义派生类的时候 必须在其构造函数中调用基类的构造函数。 实例如下:
这点和C++里面一样的是:自从继承之后,派生类里面是都有基类的属性和方法(包括其私有的)。但是基类私有的,派生类你可以继承来 但是无法访问。(也是可以使用之前访问私有属性的方式)
因为毕竟 派生类是直接继承过来了,直接看不了 间接还是可以的。
类成员的继承与重写
好的下面就小结一下:
1、成员的继承:派生类继承了除了基类的构造器以外 所有的成员(属性和方法)
2、方法的重写:即 派生类重新定义那些从基类继承来的方法。也可以称之为 覆盖
上面的这两点和我们的C++还是非常相像的。
上面就是重写了从基类继承来的方法,调用的时候 就发生了覆盖。
类继承中的层次关系
主要是通过类的 mro()方法 or 类的__mor__属性来输出整个继承关系中的 继承脉络。
OK,下面就简单的介绍一下这个 mro()方法:
由于Python是支持多继承的:若是基类里面的有些方法 名字相同了,且在派生类里面没有指定基类的名字 解释器将会按照从左到右的顺序搜索。(这个顺序就是 mro()方法打印来的顺序)所谓的MRO就是Method Resolution Order 方法解析顺序。实例如下:
解释器将会按照从左到右的顺序搜索:其实跟类定义的时候 的基类继承顺序相关。
我们前面也说了object类是所有类的基类,之后所有的类也就有了object类的属性和方法。object类的结构展示如下:
上面的dir()就把对象的所有属性 都打印出来了。
上下做一个简单的对比,会发现派生类Person对象增加了属性有:
1、_Person__age 自己的私有属性
2、__dict__
3、__module__
4、__weakref__
5、getAge 自己的方法(也是属性)
6、getName
7、name 自己的属性
其余的object对象里面拥有的全部属性都被派生类Person给继承来了;后面的方法虽是method类型,但是实质上它也是属性之一。
在Python对象中包含很多双下划线始尾的属性,这些都是特殊属性。实例如下:
obj.__dict__ 对象的属性字典
obj.__class__ 对象所属的类
class.__bases__ 类的基类元组(多继承)
class.__base__ 类的基类
class.__mro__ 类的层级结构
class.__subclasses__() 子类列表
实例如下:
# Python的特殊属性
class A:
pass
class B:
pass
class C(B,A):
def __init__(self,name):
self.name=name
class D(C):
pass
# 获得C的全部属性
print(dir(C))
# 获得对象的属性字典
print(C("songbaobao").__dict__)
# 知道对象所属的类
print(C("songbaobao").__class__)
# 得到基类元组
print(C.__bases__)
# 得到基类
print(D.__base__)
# 得到类的层级结构
print(D.__mro__)
# 得到子类列表
print(A.__subclasses__())
重写后__str__()方法
我们从上面object对象的全部属性里面会发现一个__str__()方法,其作用:用以返回一个 对对象的描述。(它对应于我们的内置函数str())它可以帮助我们查看一个对象的信息,且该方法是可以被重写的。
好的,下面我们来重写一下 对该对象的描述:
此时同样的调用方式:对于这个对象的描述 就变成了我们自定义的内容了。
Python也有多重继承
之前我们也提到了Python也有那个 被称为C++ bug的多重继承:即一个派生类可以有多个直接基类。(代码复用的程度更高;具备更多的基类内容)但是这样的结果 就直接导致如下缺陷:
1、类的整体层级结构 非常复杂
2、派生类对象的大小 急速扩大
反正在C++里面是避免使用的,当然Python也一样。
获得基类定义的方法
在派生类里面,若是想要获得基类的方法时 通常可以借助于super()方法来实现。它代表基类的定义,而非基类对象obj(而是基类 类对象)。
什么是封装
这个我们前面其实已经讲过了:隐藏对象的属性,以及实现的内部细节。(只对外提供必要的接口)。也即:把“细节”封装起来,只对外暴露“接口”的调用。 细节不要 不需要,也最好不要给用户呈现,最好只提供尽可能少 和 精简的接口。
注:我们在学习 私有的属性和方法的时候,也提到了 相较于C++严格的访问权限控制符的方式,Python选择了一种简洁的方式(没有在严格的语法层面的访问控制符):这依赖于开发人员的自觉。
什么是多态
多态性:指同一个方法调用,因调用方对象的不同而产生不同的行为。
在多态的使用上,需要注意的点有:
- 多态指的是方法的多态 属性是没有多态的
- 多态需要两个前提条件 继承、方法覆盖
上面给统一的接口函数传递的参数都是Animal基类的派生类对象,然后根据对象的派生类类型 选择不同的方法调用。
什么是组合
往往提及继承,我们总会想起另一种代码复用、设计组织代码的方式:组合。二者的区别我们在学习C++的时候就已经说过了,下面来小结一下,如下图(在学习C++ OOP的时候):
实例如下:
结果如下:
我坚决反对有人说的 使用组合来取代继承 or 继承比组合优良等等,我们应当具体问题具体分析,使用适当的方式来解决问题。
运算符重载
在Python里面,运算符的实现是基于对象的特殊方法的调用。
上面的两种实现在本质上是一模一样的。Python 一切皆对象,上面的1 2都是对象,为了操作的简便 提供了+运算符(其底层实现:借助于int对象的add方法)。
注:Python的这些运算符实质上在底层都有相应的对象的方法调用。实际如下:
运算符 | 对应方法 | 说明 |
---|---|---|
+ | __add__ |
加法 |
- | __sub__ |
减法 |
< 、<=、 == | __lt__、__le__、__eq__ |
比较运算符 |
>、>=、!= | __gt__、__ge__、__ne__ |
比较运算符 |
|、^ 、& | __or__、__xor__、__and__ |
或 异或 与 |
<<、>> | __lshift__、__rshift__ |
左移 右移 |
* 、/ 、% 、// | __mul__、__truediv__、__mod__、__floordiv__ |
乘法 浮点除 取余 整数除 |
** | __pow__ |
指数运算 |
下面就重写一个运算符,功能:计算两个人的总年龄:
Python的特殊方法
方法 | 说明 | 示例 |
---|---|---|
__init__ |
构造函数 | 创建对象 p1=Person() |
__del__ |
析构函数 | 对象回收 |
__repr__、__str__ |
打印、转换 | print(a) |
__call__ |
函数调用 | a() |
__getattr__ |
点运算符调用 | a.XXX |
__setattr__ |
属性赋值 | a.xxx=value |
__getitem__ |
索引运算 | a[key] |
__setitem__ |
索引赋值 | a[key] =value |
__len__ |
长度 | len(a) |
下面我们做一个简单的小练习:绘制不同颜色的同心圆,如下:
上面就是绘制了 三个简单的同心圆,可是当需要的数目太大的时候 就不太方便了。因此我们就需要使用上循环来完成:
若是配上颜色,则如下所示:
下面我们来再做一个练习,绘制围棋棋盘:
源码如下:
import turtle
import random
t=turtle.Pen() #拿到笔
t.width(2) #笔的粗细
colorArray=["purple","red","gold","green","blue","black"] #准备颜色
length=len(colorArray)
NUMBER=25 #棋盘空格数 24*24
WIDTH=20 #一个空格为 20*20
HALF=(NUMBER//2)*WIDTH
x=-HALF
y=HALF
for i in range(NUMBER):
y=HALF-(i*WIDTH) #调整坐标
n=random.randint(0,99)
t.penup()
t.goto(x,y) #到达起始点
t.pendown()
t.color(colorArray[n%length])
t.goto(-x,y) #先画横线
t.penup()
t.goto(-y,-x) #到达对称点
t.pendown()
t.goto(-y,x) #再画竖线
turtle.done()