5th Python面向对象基础
人狗大战的实例
需求:
多条狗,每个狗有名字,品种,攻击力
可以有多个人
狗可以咬人,人可以打狗
我们可能会这样写:

那我们如果有多条狗,我就需要写多个字典,而且还是要手动创建的,所以我们想到了函数,创建一个dog(name, d_type)
,这样我们要创建就调用这个函数即可,eg:dog('zs', 'big')
,就不用手动创建了。
那随着产品的功能升级,现在需要狗不能调用人打狗的动作,人不能调用狗咬人的动作,我们可以加一个判断来实现;人又分男人和女人,女人可以生孩子,男人不可以,我们就又要讲男人和女人的定义函数分开定义,而且又要新加判断使生孩子只能女人调用。。。。。。
可以看出,很麻烦。所以我们需要换一个思路。
5.2 编程范式——面向过程
简单介绍
面向过程,核心是“过程”这两个字。我们之前写的代码都是面向过程的编程方式(编程范式),就是我们一步一步确定先干什么在什么,然后用函数一个一个功能的实现,最后实现了我们的大目标。
总结一下,面向过程就是将一个大问题分解成多个小问题然后一一解决。
优点
解决问题的流程简单化,知道一步一步该干嘛,比如Python入门要输出只需一个print()就好,但如果是Java的话,你得先写个public static void main这样的框架。eg:我们要实现修改文件,很容易想到步骤:读取文件数据 -> 判断数据 -> 数据写入 -> 文件替换。
缺点
通过我们前面写出来的代码我们应该可以感觉的到,这样写出来的程序扩展性不是很强。很多函数都是与其他函数牵扯在一起的。函数一旦写多,找都找的不方便,也不便于维护,特别是那些需要功能需要经常变动,而且需要不断更新迭代和维护的场景。
应用场景
面向过程一般用在代码更新迭代很少的场景,其他场景最好用面向对象的编程范式。eg:Linux内核、git等等
5.3 编程范式——面向对象
简单介绍
编程范式最常用的就是面向过程和面向对象。面向对象的核心就是类与对象。面向对象就是将实际需求抽象成一个个对象,什么是对象?
说对象之前我们要说说类(class),我们可以类似理解为种类,比如人就是一个对象,包括男人、女人、老人、小孩等等等等;动物也是一个对象,包括大象、狮子、兔子等等。
和对象这样泛指的概念相对应的就是对象(object)。对象就是一个指定的一个东西,可以说类是由很多的对象构成的。eg:男人是由zm、wzw、yz、zk等等千千万万的对象组成的。
然后对象到实例的转化过程就叫做实例化,代码就和上面提到的代码很像。eg:Dog('zs', 'big')
。
PS:如果不理解可以类比数学中集合和构成集合的每个数来理解。一个类就是一个集合,集合里装的许多数就是类下实例化出来的一个个对象。
优点
解决了面向过程可扩展性较差的问题,扩展性大大提高。后面的学习过程中我们还会再说。
缺点
编程的复杂度远高于面向过程,如果不了解面向对象就上手,再基于面向对象设计程序,就极容易会出现过度设计的问题。
应用场景
因为面向对象的难度,我们在管理Linux系统的shell脚本程序中就会应用面向过程的方法。在软件开发等方向中面向对象应用广泛。
5.4 类与对象
定义语法
我们先写一个简单的类与对象的语法实例:

需要注意的点有:
类中可以写任何的python代码,这些代码在类定义的阶段便会执行,因而会产生新的名称空间,用来存放类的变量名与函数名,可以通过
类名.__dict__
来查看类中定义的名字都是类的属性,
.
是访问属性的语法对于经典类来说我们可以通过盖子点操作类名称空间的名字,但新式类有限制(经典类和新式类的区别)
类的使用
引用类的属性:

实例化类,得到一个对象:

__init__
方法:

对象的使用
对象的增删改查和类的增删改查很类似。

关于类的设置
每个程序员考虑的角度不同时,对于同一个需求定义的类可能截然不同
现实中的类并不完全等于程序中的类,比如人这个类,在程序中可能需要分成老师类、学生类等等
程序中创建的类可能在现实生活中不存在,比如策略类这个很常用的类,还比如MapReduce中的Mapper和Reducer这两个核心抽象类
经典类和新式类
经典类
class A: # 经典类
pass
新式类
class B(object): # 新式类
pass
Python中创建的一切都是对象,object可以理解为Python最原始的类,所有对象都是它衍生出来的。
我们一般写的都是经典类,在多继承那里我们会详细说它们两个的区别。
5.5 实例属性&公共属性
我们将上面的Student类修缮一下:

我们创建两个对象
stu1 = Student('zm', 19, 'male')
stu2 = Student('wzw', 23, 'male')
我们输出比较id(stu1.school)
和id(stu2.school)
,我们会发现两个对象调用的school属性的id都是一样的,我们就称school这样的属性是公有属性(类属性)。
那我们在__init__()
中也定义了一些属性,这里面的属性我们就称它们为实例属性(也称成员属性),原因前面我们提过,__init__()
是对象创建过后才将数据赋值到属性上的。
类里面的函数之间的数据是不相关的,要想实现有相互调用的数据,就需要把函数里面的值存到实例里,那就要将两个值与实例进行绑定,那我们就用到了self
。
self
代表实例(一个创建的对象,不是类!)本身,在stu1.hello()
调用实例方法时,他默认的就执行stu1.hello(stu1)
,然后函数中的self.school
就是stu2.school
。那这样的解释也可以用来解释__init__(self)
里面的self.name = name
这样的语句就是将自己内部的数据和实例进行绑定,这样hello()
里面就可以调用__init__()
里面的name
参数了。
应用情况
一般的,很多对象的属性都一样,那就可以用公共属性。而且公共属性如果被实例化以后,单独的实例修改后,就变成了给该实例创建一个新的实例属性,不会影响到公共属性的值。常见的属性有国籍、所属学校等等。
像姓名、年龄、性别这些基本每个对象都不一样的属性,我们一般用实例属性。
下面就用上面的Student类做eg:

5.6 类之间的依赖关系
就像人和人之间存在很多种关系,类与类之间也有关系,类与类之间的关系一般分为下面5种:
依赖关系:你每个月都找父母要生活费的时候,你和父母的关系;
关联关系:你和你基友或者闺蜜;
组合关系:比聚合关系更加紧密的关系,比如大脑和心脏,人死了,所有器官都会死;
聚合关系:比如电脑的各个部件,电脑坏了有的部件还是正常的;
继承关系:类的三大特性之一,子承父业。
依赖关系

关联关系

那我们可以实现一次就双向关联嘛?
答案是可以的,我们可以将关联关系单独放在一个类中,如这一段代码:

组合关系
我们接着一开始说的人狗大战,我们增加一个武器类,然后实现武器与人捆绑。

这里武器和人就是组合关系,武器自己单独不会被使用,只有创建一个人的对象时,才会被捆绑创建。
可以说,如果两个对象之间有什么数据的交互,那这两个对象就是组合关系。
5.7 面向对象三大特性之继承
继承就是子承父业,目的就是减少重复的代码。具体怎么减少,那我们在代码中就可以感觉的到。有了继承的概念就有了派生类的概念,派生类就是子类,只是必须既有父类特性又有自己的一些私有特性。
比如现在我们有人、猪、狗三个类,他们都属于动物这个类,那我们就可以将人、猪、狗的一些共性写到动物类的属性中,这样就不用写三遍重复的代码了。
继承的语法是括号:

上面的代码中,Person和Dog是继承了Animal类的,这样Person和Dog就可以不用各自单独定义__init__()
等相关重复代码了,直接调用Animal的属性和方法即可。
在上面的基础上,子类也可以定义只属于自己的方法。

那除了这些,子类还可以重写父类的方法和属性,这样也不会影响到父类的方法和属性。

那我们可以重写父类的属性和方法,那父类的__init__()
子类是否可以重写呢?这点我们单独放在一点里说,和刚才上面说的有点不一样。
重写父类的__init__()
直接上代码:

代码中都和大家说的很清楚了,我们只需要注意别忘了给父类的方法传入self
就好了。
当然,父类__init__()
的重写不止上面代码所示的一种方法,上面的方法在python2中经常使用,python3常用的方法是通过super()
来重写。

还有一种super
的写法:super().__init__(name, age, sex)
,这个相对于第二种比较方便,所以也比较常用。
当然,super()
不会只能调用父类的__init__()
,父类的所有属性和方法都可以调用。调用的语法是和__init__()
一样的,比如super().type_animal
、super().say_info()
。
我们可以发现,就是把Animal
换成super()
而已。super()
还有其他的一些应用,super()
的用处还在下面要说的多继承中有所体现。
多继承
所谓多继承就是一个类有多个父类,比如如下的代码:

MonkeyKing这个类在代码中就是多继承,同时可以调用Immortal和Monkey两个父类的属性和方法。
在各类主流语言中,C++、Python是支持多继承的,Java是不支持的。因为多继承有时可能会使程序调试变的复杂。
那多继承就会引发一个问题,如果子类调用父类中有相同名字的方法或者属性时,到底运行哪一个?比如:

我们看输出是神仙在打架,我们换一下MonkeyKing后面括号里的继承顺序,就可以看出输出就会是猴子在打架。我们就可以得出继承顺序是从左到右。
但是多继承的顺序机制没有这么简单,我们再改变一下代码:

那关系就比较复杂了,我们该如何确定查找逻辑呢?
这个继承的关系我们用图画出来就会看出是类似一个树的结构。我们常见的树结构的查找逻辑,有深度优先算法和广度优先算法。
我们通过注释输出类的方法一遍一遍的输出,我们会发现继承顺序是Monkey->MonkeyBase->Immortal->Immortal。如果我们再让Animal作为MonkeyBase的父类,顺序就是Monkey->MonkeyBase->Animal->Immortal->Immortal。那我们就可以得出,Python类的继承顺序是按照深度优先算法来继承的。
不过,但这里还没有结束。我们再继续说说。
我们前面说了经典类和新式类。在Python2中,这两个类的查找法是不一样的:经典类采用深度优先查找,而新式类采用广度优先查找。不过到了Python3,无论是经典类还是新式类,都是按广度优先查找的。(和上面的结论冲突,继续看C3算法就好)
Python2默认都是经典类,Python3默认都是新式类,所以我们不用每次都写object
,了解了就行。
多继承的C3算法
不过等等,我们实践证明出来了python3是深度优先,而上面又说是按广度优先,那到底是按照什么来查找?睁着眼说瞎话?
先别急,我们现在再改变一下代码,让ImmortalBase和MonkeyBase同时继承Base类。那我们再重复刚才的实践,我们就会发现,顺序变成了Monkey->MonkeyBase->Immortal->ImmortalBase->Base。
小朋友你是否有很多问号?????这到底是深度还是广度?
其实,Python的继承算法是C3算法,比较复杂,接下来我们慢慢来介绍。
我们再给出一个继承关系复杂的一段代码:

我们可以看到输出的继承顺序是(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.B2'>, <class '__main__.C2'>, <class 'object'>)
为了较好理解,我们画成图来给大家理解:

嗯????小朋友你是否又有很多问号?
到这里,我们只要知道反正肯定不是深度或者广度,清楚C3对于小白来说也没什么用,而且C3是Python比较底层的东西了,不用理解的太深入。在Python的官网以及其他的一些资料上都写了Python是深度优先查找,我们知道就好了。
总结
一般代码都继承都不会写到这么复杂,不然调式什么的都不好调试。
然后还需要提一下的就是,虽然python3默认都是新式类,但是官方的推荐还是要写object
的,所以我们以后就写一下就好了,专业一点。
class A(object):
pass
5.8 面向对象三大特性之封装
封装是什么
封装可以被认为是一个保护的屏障,防止该类的代码和数据被外部类定义的代码随机访问。想要访问就必须通过严格的接口控制。
适当的封装可以让程序代码更容易被维护,也加强了代码数据的安全性。
封装的优点
良好的封装能够减少耦合;
类内部的结构可以自由修改;
可以对成员变成进行更精确地控制;
隐藏信息,实现细节。
封装的原则
将不需要向外部随意操作的数据进行封装。
举些栗子
比如人狗大战的人和狗的生命值这个属性,肯定是不能让外部的代码随便修改的,所以我们需要封装生命值,这些封装的了属性就叫私有属性。
私有属性不能直接调用,只能通过类内部的一些方法来调用,这些方法即是前面说到的一些访问封装(私有属性被封装的东西等)的接口。
外部只能通过接口来操作封装的东西,你能进行什么操作(删除、获取、修改等)全靠类给你提供了什么接口。

封装的不止可以有属性,还可以封装方法,比如我们我们增加一个呼吸。

如果在外部真的想获取被封装的东西,语法是实例名._类名+封装东西的名称
,可以执行新增、修改、获取、删除等操作。
p._Person__breath() print(p._Person__life_val)
当然,pycharm可能会警告,但是输出还是会正常输出的,只是最好不要这么用,不然你封装也就没意义了。
5.9 面向对象三大特性之多态
什么是多态
现在有一个动物类Animal,子类有蛇、狗等,他们都继承Animal的eat()
方法,但是蛇和狗的吃都是不一样的,比如蛇是吞,狗是一部分一部分的咬。那这种调用同一个接口,而表示形式不同的现象,就叫做多态。
统一函数接口实现多态

抽象类实现多态(最常用)

这里的代码除了raise
那句代码我们下一章介绍,其他的代码我们应该都可以理解的。多态的应用在网络编程的原理普遍用到,虽然我们实际用到的不多,但是确实是一个很重点的点,毕竟面向对象三大特点之一。
末
这一篇我将成片的代码段变成了截图形式,大家看看什么感觉,如果还是习惯之前灰色处理的话,下一篇就还原回来哈。
有什么Python代码或者其他问题可以私信问我或者在下面留言,Python课程设计我也偶尔可以有偿帮做,祝大家变得更强[狗头]
剩下的就是和上一篇文章末尾一样要说的,我就当成套话了。
套话:因为小破站上的文本格式对演示代码极其不友好,而且自己平时的笔记是通过Markdown语法来记录的,在格式上和美观程度上不是很好看,如果你看的不习惯,就去下载一个Typora(或者支持markdown语法的应用),我这里给出md文件的迅雷和百度网盘链接,然后用Typora打开文件看就好了。
迅雷网盘链接:https://pan.xunlei.com/s/VMXpt1TptGyPt3YPLs1fEA5eA1
提取码:s7ei
百度网盘
链接:https://pan.baidu.com/s/1jUYcrmv27e6VIO8kVGu9ng
提取码:1mtr