Python的学习心得和知识总结(七)|Python 面向对象编程(二)

前面已经讲过了Python作为一门面向对象的编程语言所应有的基础知识,从现在开始重点学习OOP的三大特性:继承、封装(隐藏)和多态。

2020年5月4日11:47:54


什么是继承

继承就是可以让子类也具有父类的特性,从而达到代码复用的目的。这在设计之上就是一种增量进化:保持原父类设计的稳定性,而且可以增加新的功能 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选择了一种简洁的方式(没有在严格的语法层面的访问控制符):这依赖于开发人员的自觉。

什么是多态

多态性:指同一个方法调用,因调用方对象的不同而产生不同的行为。
在多态的使用上,需要注意的点有:

  1. 多态指的是方法的多态 属性是没有多态的
  2. 多态需要两个前提条件 继承、方法覆盖

在这里插入图片描述
上面给统一的接口函数传递的参数都是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()

猜你喜欢

转载自blog.csdn.net/weixin_43949535/article/details/105915453