编程范式概述:
面向过程 和 面向对象 以及函数式编程
面向过程:(Procedure Oriented)是一种以事件为中心的编程思想。
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。(即执行有先后顺序)
需要纵观全局,对每一个环节知根知底,每个步骤之间联系非常紧密。 支持面向过程的程序设计语言有:C语言 C++ Python等
面向对象:(Object Oriented,简称:OO)是一种以事务为中心的编程思想。
面向对象是把整体分为多个环节,对每个环节单独分析解决,最后整合到一起。 支持面向对象的程序设计语言有C++、Java、C#、Python等。
术语:面向对象的程序设计(Object Oriented Programming,简称为OOP )
函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
举例对比面向过程 和 面向对象: 例如汽车!
区别:
“面向过程”的话:汽车启动是一个事件,汽车到站是另一个事件。
编程的时候我们关心的是:某一个事件,而不是汽车本身。
然后分别对启动和到站编写程序,类似的还有修理等等。
“面向对象”则是:需要建立一个汽车的实体,由实体引发事件。
编程的时候我们关心的是:由汽车抽象成的对象,这个对象有自己的属性,像发动机:型号,马力,产地等;
有自己的方法,像启动,行驶等;方法也就是汽车的行为,而不是汽车的每个事件。
联系:
“面向过程”其实是最为实际的一种思考方式;就算是面向对象的方法,也含有面向过程的思想。
可以说面向过程是一种基础的方法,它考虑的是实际功能的实现。一般的面向过程是从上往下步步求进,所以面向过程最重要的是模块化的思想方法。
而“面向对象”的方法主要是把事物给对象化,对象包括属性与行为。
当程序规模不是很大时,“面向过程”的方法还会体现出一种优势,因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。
但是,当程序规模较大的时候,“面向对象”的方法优势就会明显的体现出来:具有程序结构清晰,实现简单,
可有效地减少程序的维护工作量,代码重用率高,软件开发效率高等优点。
另一个通俗的例子:印刷术
面向过程:传统的雕版印刷,把整个板子刻满字,每次印都要刻一块
面向对象:毕昇的活字印刷,把每个字分别雕好,用的时候拼到一起
因为面向过程的联系紧密,单个步骤出错对整体影响比较大,比如整本书需要雕10块木板,结果雕错一个字,一整块木板都要扔掉重新刻。
实例:用函数式编程的方式 实现打印输出如下内容:
小明,10岁,男,上山去砍柴
小明,10岁,男,开车去东北
小明,10岁,男,最爱打游戏
老李,90岁,男,上山去砍柴
老李,90岁,男,开车去东北
老李,90岁,男,最爱打游戏
def foo (name,age,gender,content): print(name,age,gender,content) foo("小明","10岁","男","上山去砍柴") foo("小明","10岁","男","开车去东北") foo("小明","10岁","男","最爱打游戏") foo("老李","50岁","男","上山去砍柴") foo("老李","50岁","男","开车去东北") foo("老李","50岁","男","最爱打游戏")
#===========>>> 上边写法的不足: 参数调用 存在问题
#将上方的实例 改写成面向对象 class Bar: def foo(self,name,age,gender,content): print(self,name,age,gender,content) obj = Bar() obj.foo("小明","10岁","男","上山去砍柴") obj.foo("小明","10岁","男","开车去东北") obj.foo("小明","10岁","男","最爱打游戏") #输出结果:其中,self 生成的了引用对象地址 #<__main__.Bar object at 0x0000000000705320> 小明 10岁 男 上山去砍柴 #<__main__.Bar object at 0x0000000000705320> 小明 10岁 男 开车去东北 #<__main__.Bar object at 0x0000000000705320> 小明 10岁 男 最爱打游戏 #上方改成了面向对象的方式 但是参数的传递 不够简洁 待优化!
一 定义:
函数:
def + 函数名(参数)
面向对象:
class 类=>> 上方名字叫Bar类
def 方法=>> 上方实例中叫foo
### self 此处必须写。 代指:调用方法的对象(中间人)这个参数是Python自动传递的。
二 执行:
函数:
函数名(参数)
面向对象:
obj = Bar() # 相当于创建了中间人,这个中间人就是一个对象。
obj.foo
理解方法中的self参数:
(1)当程序执行到obj = Bar()的时候,解释器会根据类名Bar()创建了一个中间人(对象),我们可以理解为是:对象应用了类
(2)当中间人(对象)obj执行.foo方法的时候,首先根据上方关联的类名查找.foo方法 然后执行foo方法。
(3)foo中的self 必须写,代指:调用方法的对象(中间人)该参数是Python自动传递的;执行之后,self输出结果,生成的是一个对象引用地址。
(4)中间人(对象)中可以添加参数,即:可以进行值传递。
self 代指:执行当前方法的对象!
而这个‘当前’需要另外注意:在涉及到继承关系的时候,无论对象是调用执行当前类中的方法,还是继承调用父类中的方法,self都是代指外部最开始调用相应方法的对象!
# 实例:理解方法中的self
class Bar: def foo(self,arg): print(self,self.name,self.age,arg) zhong_obj1 = Bar() # 中间人(对象1) zhong_obj1.name = 'jesson' # 说明:中间人(对象)中可以带值 当作参数传递给类里边方法中的self zhong_obj1.age = '25' # 说明:中间人(对象)中可以带有多个值 当作参数传递 zhong_obj1.foo(888) zhong_obj2 = Bar() # 中间人(对象2) 说明:同一个类 可以被多个对象引用 zhong_obj2.name = 'pitter' zhong_obj2.age = '20' zhong_obj2.foo(666) # 输出结果: # <__main__.Bar object at 0x0000000000A65390> jesson 25 888 # <__main__.Bar object at 0x0000000000A65400> pitter 20 666
# 优化上方 面向对象改写的实例 [给对象存放值 当作参数传递]
#优化一 class Bar: def foo(self,contnt): print(obj.name,obj.age,obj.gender,contnt) obj = Bar() obj.name = '小明' obj.age = 10 obj.gender = '男' obj.foo("上山去砍柴") obj.foo("开车去东北") obj.foo("最爱打游戏")
上方的优化 已经基本符合要求
但是还不够简洁 Python这个时候 提供了一种方式,即:构造方法 __init__() 用来封装相关对象默认字段的值
构造方法: 类名后边加括号 自动执行__init__方法 (即:实例化对象的时候,就会执行__init__方法)
特殊作用: [初始化] 用来存放一些默认字段
obj = 类名()
(1)创建对象
(2)通过对象执行类中的一个方法
#采用 构造方法__init__() 对上述实例进一步优化: class Bar: def __init__(self,n,a,g): self.name = n self.age = a self.gender = g def foo(self,contnt): print(self.name,self.age,self.gender,contnt) obj = Bar("小明",10,"男") obj.foo("上山去砍柴") obj.foo("开车去东北") obj.foo("最爱打游戏")
小结:
通过上述实例的优化对比,可以看出,如果使用函数式编程,
需要在每次执行函数时传入相同的参数,如果参数多的话,又需要粘贴复制了..;
而对于面向对象只需要在创建对象时,将所有需要的参数封装到当前对象中,
之后再次使用时,通过self间接去当前对象中取值即可。
面向对象适用场景:
如果多个函数中有一些相同参数时,转换成面向对象。
面向对象的三大特性:
面向对象的三大特性是指:封装、继承和多态。
一、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
将内容封装到某处
从某处调用被封装的内容
二、继承
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
# 基本用法: class 父类: def 父类中的方法(self): class 子类(父类): pass son = 子类() son.父类中的方法()
实例:
class Father: def f1(self): print('Father.f1') def f2(self): print('Father.f2') class Son(Father): def s1(self): print('Son.s1') obj = Son() obj.s1() obj.f1() # 输出结果: # Son.s1 # Father.f1 # 继承了父类 调用执行父类中的f1方法。
我们知道 如果子类继承了父类 则可以调用父类中的所有方法
那 我们可不可以 调用的时候 不去继承执行父类的方法 而指定执行自己内部的方法呢?
答案:肯定是可以的!
情况1: 不执行父类的方法 执行自己的同名方法
在本地重写和父类中同名的方法 即:在自己内部写一个同名的方法:f2
实例: class Father: def f1(self): print('Father.f1') def f2(self): print('Father.f2') class Son(Father): def s1(self): print('Son.s1') def f2(self): print('Son.f2') obj = Son() obj.s1() # 输出结果:Son.s1 obj.f2() # 输出结果:Son.f2
# 情况2: 即可以执行父类的方法 同时也可以执行自己的同名方法
# 这里可以调用Python内置方法 super()
# 实例: class Father: def f1(self): print('Father.f1') def f2(self): print('Father.f2') class Son(Father): def s1(self): print('Son.s1') def f2(self): # 方式一 super(Son,self).f2() # 方式二 Father.f2(self) print('Son.f2') obj = Son() # 输出结果:Son.s1 obj.s1() # 输出结果:Father.f2 obj.f2() # 输出结果:Son.f2
# *重点* 多继承的查找顺序# 先来看实例:
# 情况1:
# 子类继承两个父类 两个父类中都有子类调用的方法 该如何继承?
补充注意:子类继承多个父类的写法,即支持多继承!在C++和Python中是支持的,但 在C#和Java中,是不支持的,他们为了避免继承混淆,通常只支持子类继承一个父类的写法!
class Father1: def a(self): print('Father1.a') class Father2: def a(self): print('Father2.a') class Son(Father2,Father1): pass obj = Son() obj.a() # 输出结果:说明:这种情况下 从左往右继承 # Father2.a
结论:
情况2
子类Son继承两个父类 其中一个父类Father1没有该子类调用的方法
而该父类又继承了别的类Grand Grand类中有子类Son调用的方法 然而另一个父类Father2中,也有该子类Son调用的方法,
此时 是找完Father1没找到,继续往上层Grand里边找,还是返回到另一个父类Father2中找? 该如何继承?
看图:
class Grand: def a(self): #def b(self): #如果将a方法改成b 则会返回父类Father2中查找方法a print('Grand.a') class Father1(Grand): def b(self): print('Father1.b') class Father2: def a(self): print('Father2.a') class Son(Father1,Father2): pass obj = Son() obj.a() # 输出结果:结论:这种情况下 先从左往右继承Father1,发现找不到方法a,继续往上层找,如果还找不到,就返回到另一个父类Father2中查找。 # Grand.a
#情况三
#看图:
Python继承调用顺序 总结:【以下两个结论是在Python3环境下,得出的,也就是针对新式类而言!!!】
情况1:
如果没有共同的基类(即:继承结构顶部没有交集),那么继承调用的时候,默认按照子类括号中的继承顺序,优先从左往右找,继承第一个父类,然后‘一条路走到黑’(即:一直往下找,找不到相应方法,才会返回来走第二个父类);
情况2:
如果有共同的基类(即:继承结构顶部有交集),那么子类调用父类方法的时候,首先,默认还是按照括号中的继承顺序,从左往右找,如果父类中没有,父类的父类中也没有,而子类继承的右边第二个父类方法中有相应的方法,此时即使顶部交集的基类中有相应的方法,也不会继承调用基类,而是会返回来优先继承第二个父类中的方法;如果第二个父类方法中,没有相应的方法,则会像情况1那样,继承调用基类中的方法。简单来记,就是,子类继承父类的时候,如果继承结构顶部有交集,存在共同的base基类,那么base基类中的方法,被调用的优先级最低!
补充说明:Python面向对象,继承多个父类调用的时候,永远要清楚,调用方法的self到底代指的对象是谁!当遇到特殊情况,比如,子类对象son_obj继承调用父类中的方法func1,而父类中的该方法func1又调用指定了其他的方法func2,这个时候就要注意了,此时的self还是子类的那个对象,而不是说现在调用执行了父类中的方法func1,self就变成父类的对象;因此,此时即使父类中本身就有方法func2,子类也不一定会继承,因为此时的self对象仍旧是子类的son_obj对象;而是会这样执行,子类优先查找右边的第二父类中是否有相应的方法func2,有的话继承调用,没有的话,才会继承刚才父类中本身自带的方法func2!
经典类(继承 从左往右,深度优先)
新式类(继承 从左往右,广度优先)
对于Python3中全是新式类。
对于Python2中有新式类和经典类之分,创建类的时候如果继承了(object)那么该类就是新式类;没有继承的话,则是经典类。
这里并不矛盾,上述结论同样适用于Python2,因为,Python创建新式类需要继承(object),这就相当于结论2中多个父类的顶部,有共同的基类,这种情况下遵循广度优先(即:新式类);而对于经典类,继承多个父类的顶部并没有交集,那么这个时候,就是一条路走到黑,深度优先。
结论:Python3虽然都是新式类了,继承的算法是基于C#算法,但是和Python2的继承规则实际上是完全吻合的。
另外,虽然我们暂时搞明白多个继承的调用顺序了,但是,复杂的多层父类继承编程方式,是不提倡的,这样可能会降低Python代码的整体执行效率。
# 多态
可以理解成:
例如:一个类为class A(name,age):
然后实例化的时候,student1 = A('jesson',22)
student2 = A('Tim',20)
这个时候,对象实例化的时候,由于传递的参数不同,实例化之后的对象是不同的。 可样,可以简单的理解成面向对象的多态。
体现:子类可以继承父类的方法,也可以调用自己的方法。