zoukankan      html  css  js  c++  java
  • 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

    
    
  • 相关阅读:
    浅析Java中使用AES对称加密步骤解析、SpringBoot如何实现AES加解密(秘钥、偏移量)、Java AES加解密工具类参考示例
    浅析Vue项目如何基于Vuex进行qiankun微前端应用间通信方案实践:子应用无vuex共用主应用store的方案(子应用里如何保证主应用store在子应用的响应式)、子应用有独立store与主应用store分离共存的方案
    浅析CSS中过渡transition学习:animation与transition的区别、过渡的4个属性及简写模式、过渡触发方式、过渡渐变(需绝对值)、如何使用硬件加速、过渡时间函数、过渡结束回调事件
    浅析Nginx配置获取客户端真实IP的proxy_set_header、XRealIP、$remote_addr、XForwardedFor、$proxy_add_x_forwarded_for分别是什么意思
    浅析Svelte介绍了解、如何看待 svelte 这个前端框架、svelte评测结论(存在一个阈值大小优势消失、适用简单应用场景又想要数据驱动方式)
    浅析什么是设计模式(套路)、为什么需要设计模式(最优解决方案)、前端常见设计模式(策略模式、发布订阅模式、装饰器模式、适配器模式、职责链模式、代理模式)
    浅析微前端qiankun的2种应用间通信方式(actions通信及shared通信、各自通信原理及实例代码)
    浅析mysql中查询使用 != 不等于会过滤掉null的情况及其原因分析和解决、IFNULL 函数用法及其使用需要注意的事项
    【Java】finally用法
    【Redis】单线程理解及可能影响性能的操作
  • 原文地址:https://www.cnblogs.com/hellojesson/p/5893732.html
Copyright © 2011-2022 走看看