python编程 基础入门学习笔记六——面向对象编程
本节内容
1.面向对象编程与面向过程编程的区别
2.面向对象编程:类和对象
3.面向对象的特性:封装、继承、“多态”
1.面向对象编程与面向过程编程的区别
先了解几个概念:
- 面向过程编程:根据业务逻辑从上到下写代码
- 函数式编程:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
- 面向对象编程:对函数进行分类和封装,让开发“更快更好更强…”
面向过程编程:分析出解决问题所需要的步骤,用代码一步步的实现(例如python编程 基础入门练习题一——登录接口及三级菜单、python编程 基础入门练习题二——购物车
的编写就是面向过程编程)。
函数式编程:在面向过程编程(先分析出解决问题所需要的步骤)的基础上,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了(例如python编程 基础入门练习题五——python计算器的编写就是函数式编程)。
面向对象编程:先把构成问题的事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。面向对象是以功能来划分问题,而不是步骤。
可以拿生活中的实例来理解面向过程与面向对象,例如飞行棋。
面向过程的设计思路就是先分析问题的步骤:1、开始游戏 2、A掷骰子 3、根据点数按照规则选择路线 4、判断是否到达终点 5、轮到B掷骰子 6、根据点数按照规则选择路线 7、判断是否到达终点 8、轮到C掷骰子 9、根据点数按照规则选择路线 10、判断是否到达终点 11、轮到D掷骰子 12、根据点数按照规则选择路线 13、判断是否到达终点 14、返回步骤2,15、输出最后结果。
把上面每个步骤用面向对象的设计思想来解决。
面向对象的设计思路则是这样的:整个飞行棋可以分为1、A、B、C、D四方,这四方的行为是一模一样的 2、棋盘系统,负责绘制飞行图 3、规则系统,负责判定诸如起飞、连投奖励、撞子、跳子等等。第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子飞行线路图,棋盘对象接收到了玩家对象(骰子点数)就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
同样是绘制飞行棋线路,在面向过程的设计中,绘图分散在了多个步骤中。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
2.面向对象编程:类和对象
面向对象编程(Object Oriented Programing,OOP)的实现是基于对 类 和 对象 的使用。
类(class) 是一个模板,模板中包装了多个“函数”供使用
对象(object) 是根据模板创建的实例(即:对象),通过实例可以调用执行被包装在类中的函数
#-*- coding:utf-8 -*- #创建类 class ClassName: #创建类中的函数 def Bar(self): print("创建类中的Bar函数") #do something #根据类ClassName创建对象obj obj=ClassName() #通过实例对象可以调用执行类中的函数 obj.Bar()
- class是关键字,表示类,ClassName是类名
- 创建对象obj,类名后加括号ClassName()即可
注意:类中的函数第一个参数必须是self;类中定义的函数叫做 “方法”
3.面向对象的特性:封装、继承、"多态"
面向对象编程的三大特性:封装、继承、多态。
1)封装
封装,就是把内容封装在某处,需要使用时再去调用被封装在某处的内容。
#-*- coding:utf-8 -*- #创建类 class ClassName: #创建构造函数,在创建类的对象时自动执行 def __init__(self,name,tel): self.name=name self.tel=tel #根据类ClassName创建对象obj #自动执行ClassName类的__init__方法 obj=ClassName("Jack","188-8888-8888") #将Jack和188-8888-8888分别封装到obj和self的name及tel属性中 print(obj.name,obj.tel)
self是一个形式参数
执行obj=ClassName("Jack","188-8888-8888")时,self等于obj
所以,内容其实被封装到了对象obj中,obj对象中就有了name和tel属性
这里
print(obj.name,obj.tel)
通过对象直接调用被封装的内容。
还有一种情况,是通过self间接调用被封装的内容:
#-*- coding:utf-8 -*- #创建类 class ClassName: #创建构造函数,在创建类的对象时自动执行 def __init__(self,name,tel): self.name=name self.tel=tel #创建类中的函数 def Bar(self): print("我是类中的Bar方法") print(self.name) print(self.tel) #根据类ClassName创建对象obj #自动执行ClassName类的__init__方法 obj=ClassName("Jack","188-8888-8888") #将Jack和188-8888-8888分别封装到obj和self的name及tel属性中 #通过实例对象可以调用执行类中的函数 obj.Bar()
obj.Bar(),默认将obj传给self参数,即:obj.Bar(obj),所以,此时方法内部的 self = obj,即:self.name 是 Jack ;self.tel 是 188-8888-8888。
总结:对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者通过self间接获取被封装的内容。
2)继承
继承——父类与子类的关系,目的之一是代码重用。
#-*- coding:utf-8 -*- class Father: def __init__(self,name): self.name=name print("调用父类构造函数") def fatherMethod(self): print("调用父类方法") print("Hello,This is %s" %self.name) class Son(Father): def __init__(self,name): self.name=name print("调用子类构造函数") def sonMethod(self): print("调用子类方法") f=Father("Rose") f.fatherMethod() print("分割线".center(100,"*")) s=Son("Jim") s.sonMethod() s.fatherMethod() #派生类在调用基类方法时,一定要注意,构造函数参数变量的一致性
继承的一些特点:
1)派生类(子类)继承基类(父类),即派生类(子类)拥有了基类(父类)中所有方法;
2)在继承中,基类的构造函数(__init__()方法)不会自动被调用,如果一定要调用,需要在其派生类的构造函数中专门调用;
3)python总是先查找对应类的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找;(即现在本类中查找调用方法,找不到才去基类中找)
4)在继承中,要调用基类的方法时,需要在派生类的构造函数中带上self参数变量。
看段简单的代码,也许你就更清晰了
#-*- coding:utf-8 -*- #创建基类:总公司员工 class Headquarters_Employee: def __init__(self,name,sex,age,edu,department): print("调用父类构造函数") self.name=name self.sex=sex self.age=age self.edu=edu #学历 self.department=department #部门 #信息录入 def import_info(self): print("调用父类方法") print("姓名:%s\n性别:%s\n年龄:%d\n学历:%s\n部门:%s" %(self.name,self.sex,self.age,self.edu,self.department)) #创建派生类:上海分公司员工 class Shanghai_Employee(Headquarters_Employee): def __init__(self,name,sex,age,edu,department): print("调用子类构造函数") self.name=name self.sex=sex self.age=age self.edu=edu #学历 self.department=department #部门 #上海员工管理 def manage(self): print("调用子类方法") print("中国上海分公司员工管理方法") f1=Headquarters_Employee("Rose","Female",26,"master","IT") f1.import_info() f2=Headquarters_Employee("Jim","Male",20,"master","IT") f2.import_info() print("分割线".center(100,"*")) s1=Shanghai_Employee("张三","男",20,"硕士","技术部") s1.import_info() s1.manage()
总结:对于面向对象的继承来说,其实就是将多个类共有的方法提取到基类中,派生类仅需继承基类而不必一一实现每个方法。
看到这里,善于思考的你,也许就会有疑问了:
1)是否可以多继承?
2)如果可以多继承,那么继承的多个类都定义了相同名称的函数,哪一个会被调用呢?
首先回答以上问题:
1)python的类可以继承多个类(注意,在以往学习的Java和C#中只能继承一个类)。
2)在多继承中,在调用方法的时候,有两种:深度优先和广度优先。
在详细分解上述两个答案之前,我先要引入两个术语:经典类、新式类。
class A:#基类 pass class B(A):#派生类 pass
这里A是经典类,派生出来的B也是经典类。
class A(object):#基类 pass class B(A): pass
这里A是新式类,派生出来的B也是新式类。
特别说明:Python 2.x中默认都是经典类,只有显式继承了object才是新式类。
Python 3.x中默认都是新式类,不必显式的继承object,也就是说Python3.x没有经典类之说。
既然提到了新式类和经典类,我们就来讲解深度优先和广度优先。
#-*- coding:utf-8 -*- #Author:'Yang' #经典类多继承:深度优先 class A: def foo(self): print("A.foo") class B(A): # def foo(self): # print("B.foo") def func(self): print("B.func") class C(A): def foo(self): print("C.foo") class D(B,C): def func(self): print("D.func") # def foo(self): # print("D.foo") objD=D() '''执行foo方法时,首先去D类中查找,若D类中没有,继续去B类中找,B类也没有, 则继续去A类中找,A类中还是没有,再去C类中找,都没找到,则抛错''' #D->B->A->C objD.foo() objD.func()
代码执行结果:
A.foo
D.func
#-*- coding:utf-8 -*- #Author:'Yang' #新式类多继承:广度优先 class A(object): def foo(self): print("A.foo") # def func(self): # print("A.func") class B(A): def func(self): print("B.func") class C(A): def foo(self): print("C.foo") def func(self): print("C.func") class D(B,C): def func(self): print("D.func") # def foo(self): # print("D.foo") objD=D() '''执行foo方法时,首先去D类中查找,若D类中没有,继续去B类中找,B类也没有, 则继续去C类中找,C类中还是没有,再去A类中找,都没找到,则抛错''' #D->B->C->A objD.foo() objD.func()
C.foo
D.func
Python 3.x中默认都是新式类,不必显式的继承object,也就是说Python3.x没有经典类之说,所以Python3.x的多继承是广度优先。
Python 3.x中默认都是新式类,不必显式的继承object,也就是说Python3.x没有经典类之说,所以Python3.x的多继承是广度优先。
Python 3.x中默认都是新式类,不必显式的继承object,也就是说Python3.x没有经典类之说,所以Python3.x的多继承是广度优先。
重要的事说三遍!!!
3)“多态”
Python不支持多态,所以不存在多态之说。多态的概念是应用于Java和C#等等强类型语言中。
Python是“鸭子类型”的语言,“鸭子类型”的语言是这么推断的:一只鸟走起来像鸭子、游起泳来像鸭子、叫起来也像鸭子,那它就可以被当做鸭子。也就是说,它不关注对象的类型,而是关注对象具有的行为(方法)。
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的走和叫方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
class F1: print("F1") class S1(F1): def show(self): print("S1.show") class S2(F1): def show(self): print("S2.show") #Java或C#等强类型语言需要这样定义接口函数 '''由于在Java或C#中定义函数参数时,必须指定参数的类型,为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,派生了一个S1和S2类,而实际传入的参数是:S1对象和S2对象''' def Func(F1 obj): print(obj.show()) s1_obj=S1() Func(s1_obj) s2_obj=S2() Func(s2_obj)
#-*- coding:utf-8 -*- #Author:'Yang' class F1: print("F1") class S1(F1): def show(self): print("S1.show") class S2(F1): def show(self): print("S2.show") def Func(obj): print(obj.show()) s1_obj=S1() Func(s1_obj) s2_obj=S2() Func(s2_obj)
思考:
什么情况下使用面向对象编程?什么情况下使用函数式编程呢?
一般在Python开发中,全部使用面向对象编程 或 面向对象编程和函数式编程混合使用。
因为,对于Python语言同时支持两种编程方式,且函数式编程能完成的操作面向对象编程都可以实现;而面向对象编程能完成的操作,函数式编程无法实现(函数式编程无法实现面向对象编程的封装功能。)
题外话:对于 C# 和 Java 程序员来说不存在这个问题,该两门语言只支持面向对象编程(不支持函数式编程)。
4.练习
选课系统
角色:学校、学员、课程、讲师
要求:
1. 创建北京、上海 2 所学校
2. 创建linux , python , go 3个课程 , linux\py 在北京开, go 在上海开
3. 课程包含,周期,价格,通过学校创建课程
4. 通过学校创建班级, 班级关联课程、讲师
5. 创建学员时,选择学校,关联班级
5. 创建讲师角色时要关联学校,
6. 提供两个角色接口
1) 学员视图, 可以注册, 交学费, 选择班级,
2 )讲师视图, 讲师可管理自己的班级, 上课时选择班级, 查看班级学员列表 , 修改所管理的学员的成绩
3) 管理视图,创建讲师, 创建班级,创建课程
7. 上面的操作产生的数据都通过pickle序列化保存到文件里
浙公网安备 33010602011771号