欢迎光临散文网 会员登陆 & 注册

5th Python面向对象基础

2021-04-09 17:22 作者:MoNanGo  | 我要投稿

5.1 引子

人狗大战的实例

需求:

  • 多条狗,每个狗有名字,品种,攻击力

  • 可以有多个人

  • 狗可以咬人,人可以打狗

我们可能会这样写:

那我们如果有多条狗,我就需要写多个字典,而且还是要手动创建的,所以我们想到了函数,创建一个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种:

  1. 依赖关系:你每个月都找父母要生活费的时候,你和父母的关系;

  2. 关联关系:你和你基友或者闺蜜;

  3. 组合关系:比聚合关系更加紧密的关系,比如大脑和心脏,人死了,所有器官都会死;

  4. 聚合关系:比如电脑的各个部件,电脑坏了有的部件还是正常的;

  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_animalsuper().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 面向对象三大特性之封装

封装是什么

封装可以被认为是一个保护的屏障,防止该类的代码和数据被外部类定义的代码随机访问。想要访问就必须通过严格的接口控制。

适当的封装可以让程序代码更容易被维护,也加强了代码数据的安全性。

封装的优点

  1. 良好的封装能够减少耦合;

  2. 类内部的结构可以自由修改;

  3. 可以对成员变成进行更精确地控制;

  4. 隐藏信息,实现细节。

封装的原则

  • 将不需要向外部随意操作的数据进行封装。

举些栗子

比如人狗大战的人和狗的生命值这个属性,肯定是不能让外部的代码随便修改的,所以我们需要封装生命值,这些封装的了属性就叫私有属性。

私有属性不能直接调用,只能通过类内部的一些方法来调用,这些方法即是前面说到的一些访问封装(私有属性被封装的东西等)的接口。

外部只能通过接口来操作封装的东西,你能进行什么操作(删除、获取、修改等)全靠类给你提供了什么接口。


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


如果在外部真的想获取被封装的东西,语法是实例名._类名+封装东西的名称,可以执行新增、修改、获取、删除等操作。

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


5th Python面向对象基础的评论 (共 条)

分享到微博请遵守国家法律