Python 面向对象 基础

编程范式概述:
面向过程 和 面向对象 以及函数式编程
面向过程:(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

ae319a13-1266-418a-b785-dc8a371cacad

# 优化上方 面向对象改写的实例 [给对象存放值 当作参数传递]

#优化一
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("最爱打游戏")
image

上方的优化 已经基本符合要求
但是还不够简洁 Python这个时候 提供了一种方式,即:构造方法 __init__() 用来封装相关对象默认字段的值

构造方法: 类名后边加括号 自动执行__init__方法   (即:实例化对象的时候,就会执行__init__方法)
特殊作用: [初始化] 用来存放一些默认字段
obj = 类名()
(1)创建对象
(2)通过对象执行类中的一个方法

image
#采用 构造方法__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("最爱打游戏")
image

小结:
通过上述实例的优化对比,可以看出,如果使用函数式编程,
需要在每次执行函数时传入相同的参数,如果参数多的话,又需要粘贴复制了..;
而对于面向对象只需要在创建对象时,将所有需要的参数封装到当前对象中,
之后再次使用时,通过self间接去当前对象中取值即可。

面向对象适用场景:
如果多个函数中有一些相同参数时,转换成面向对象。
面向对象的三大特性:
面向对象的三大特性是指:封装、继承和多态。

一、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
将内容封装到某处
从某处调用被封装的内容

二、继承
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。

# 基本用法:
class 父类:
    def 父类中的方法(self):

class 子类(父类):
    pass

son = 子类()
son.父类中的方法()

 image

实例:

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
image

# *重点* 多继承的查找顺序# 先来看实例:
# 情况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

结论:

image

情况2
子类Son继承两个父类 其中一个父类Father1没有该子类调用的方法
而该父类又继承了别的类Grand Grand类中有子类Son调用的方法 然而另一个父类Father2中,也有该子类Son调用的方法,
此时 是找完Father1没找到,继续往上层Grand里边找,还是返回到另一个父类Father2中找? 该如何继承?
看图:

image
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

#情况三

#看图:
image

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)

这个时候,对象实例化的时候,由于传递的参数不同,实例化之后的对象是不同的。 可样,可以简单的理解成面向对象的多态。

体现:子类可以继承父类的方法,也可以调用自己的方法。

多态:http://www.cnblogs.com/hai-ping/articles/2807750.html


原文地址:https://www.cnblogs.com/hellojesson/p/5893732.html