• Py修行路 python基础 (十六)面向对象编程的 继承 多态与多态性 封装


    一、继承顺序:

      多继承情况下,有两种方式:深度优先和广度优先

    1、py3/py2 新式类的继承:在查找属性时遵循:广度优先

      继承顺序是多条分支,按照从左往右的顺序,进行一步一步查找,一个分支走完会走另一个分支(若多条分支汇总一个头,除最后一条分支能走到头,其他的都走到次之位置停止,进行下一条分支的查找),直到查找到头为止。

    可以利用 类名.__mro__ 的方法查看类之间的继承关系(经典类没有这种方法)

     1 class B(object):
     2     def func(self):
     3         print('-----> B')
     4     pass
     5 class C(object):
     6     def func(self):
     7         print('-----> C')
     8     pass
     9 class D(B,C):
    10     def func(self):
    11         print('-----> D')
    12     pass
    13 class E(B,C):
    14     def func(self):
    15         print('-----> E')
    16     pass
    17 class F(D,E):
    18     def func(self):
    19         print('-----> F')
    20     pass
    21 # f=F()
    22 # f.func()
    23 print(F.__mro__)
    24 
    25 #执行结果:
    26 (<class '__main__.F'>, <class '__main__.D'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)

     2、py2 经典类的继承:在查找属性时遵循:深度优先

      一个子类继承多个父类,可以看作是多个分支,依然是遵循从左到右的顺序,只是第一条分支从尾走到头,找不到就会再走别的分支,只是最开始的父类不会再找。

    3、继承原理
      python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表
    为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。

     合并所有父类的MRO列表遵循如下三条准则:
      1.子类会先于父类被检查
      2.多个父类会根据它们在列表中的顺序被检查
      3.如果对下一个类存在两个合法的选择,选择第一个父类

    子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
    方法一:父类名.父类方法()
    方法二:super()

      两种方式,虽然都能调用,但是方法一存在极大的局限性,需要明确父类名,假如父类不存在,直接结果就会导致子类调用执行时报错,而super()是按照新式类的继承顺序,利用 super().函数名(参数) 的方式,继承父类方法。

      使用super()函数时,Python会在MRO列表上继续搜索下一个类。只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。

      注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表

    super在python2中的用法:
    1:super(自己的类,self).父类的函数名字
    2:super只能用于新式类
     1 #coding:utf-8
     2 #super在python2中的用法:
     3     # 1:super(自己的类,self).父类的函数名字
     4     # 2:super只能用于新式类
     5 class People(object):
     6     def __init__(self,name,sex,age):
     7         self.name=name
     8         self.age=age
     9         self.sex=sex
    10     def walk(self):
    11         print('%s is walking' %self.name)
    12 class Chinese(People):
    13     country='China'
    14     def __init__(self,name,sex,age,language='Chinese'):
    15         # self.name=name
    16         # self.sex=sex
    17         # self.age=age
    18         # People.__init__(self,name,sex,age)
    19         super(Chinese,self).__init__(name,sex,age)
    20         self.language=language
    21 c=Chinese('egon','male',18)
    22 print c.name,c.age,c.sex,c.language
    23 
    24 #执行结果:
    25 egon 18 male Chinese
    
    
     1 #在python3中
     2 class People:
     3     def __init__(self,name,sex,age):
     4         self.name=name
     5         self.age=age
     6         self.sex=sex
     7     def walk(self):
     8         print('%s is walking' %self.name)
     9 class Chinese(People):
    10     country='China'
    11     def __init__(self,name,sex,age,language='Chinese'):
    12         super().__init__(name,sex,age)#super()函数.父类函数名(参数)
    13         # 由于super()相当于是带入一个类而不在是函数,所以传值的时候,不再需要给self传值,实例化的时候自动带入。
    14         self.language=language
    15     def walk(self,x):
    16         super().walk()  #super()函数.父类函数名()
    17         print('%s is chase %s'%(self.name,x))
    18 c=Chinese('anyone','male',18)
    19 print(c.name,c.age,c.sex,c.language)
    20 c.walk('somebody')
    21 
    22 #执行结果:
    23 anyone 18 male Chinese
    24 anyone is walking
    25 anyone is chase somebody

    二、多态和多态性:

    1、定义:

      多态(是从定义角度出发):同一类事物的多种形态。(一个抽象类有多个子类,因而多态的概念依赖于继承)例如:动物的多种形态:人,狗,猪。

      多态性(是从使用角度出发):同一种调用方式,不同的执行效果。具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。

      多态性依赖于:1.继承;2.定义的接口
      #定义统一的接口,可以传入不同类型的值,但是调用的逻辑都一样,但是执行的结果却不一样。

     1 #多态:同一种事物的多种形态,动物分为人类,猪类(在定义角度)
     2 class Animal:
     3     def run(self):
     4         raise AttributeError('子类必须实现这个方法')
     5 
     6 class People(Animal):
     7     def run(self):
     8         print('人正在走')
     9 
    10 class Pig(Animal):
    11     def run(self):
    12         print('pig is walking')
    13 
    14 class Dog(Animal):
    15     def run(self):
    16         print('dog is running')
    17 
    18 peo1=People()
    19 pig1=Pig()
    20 d1=Dog()
    21 #实例化调用方法得到的结果
    22 # peo1.run()
    23 # pig1.run()
    24 # d1.run()
    25 
    26 #多态性:一种调用方式,不同的执行效果(多态性)
    27 # 多态性依赖于:
    28 #     1.继承
    29 #     2.
    30 ##多态性:定义统一的接口,
    31 def func(obj): #obj这个参数没有类型限制,可以传入不同类型的值
    32     obj.run() #调用的逻辑都一样,执行的结果却不一样
    33 
    34 func(peo1)
    35 func(pig1)
    36 func(d1)
    37 
    38 #执行结果:
    39 人正在走
    40 pig is walking
    41 dog is running

      多态性的实质:实质就是定义了一个函数接口,在这个函数中定义了所有类内通性的功能,只要传入参数(对象名)函数调用执行,就得到不同的结果。这就是所谓的一种调用方式,不同的执行结果。

    2、 多态性的好处:
     1、增加了程序的灵活性
      以不变应万变,不论对象千变万化,使用者都是同一种形式去调用
     2、增加了程序的可扩展性
      通过继承父类创建了一个新的子类,使用者无需更改自己的代码,还是用定义的接口函数去调用。     

    三、封装:
      1、封装的本质就是隐藏,将一些复杂的执行过程隐藏起来,留下调用的接口(接口就是函数,称为接口函数;一组接口函数的集合体构成一个接口),我们通过这些接口进行交互,不管程序在内部怎么个应用流转方式,只为得到最后的结果。

     2、数据封装主要原因是:保护隐私方法封装主要原因是:隔离复杂度

     3、封装分为两个层面:

      但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口

    第一层面的封装:其实就是创建类或是对象,通过类名.或对象名.的方式调用对应的方法,这本身就是一种封装。
      注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

    第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。

      1、在python中用双下划线的方式实现隐藏属性(设置成私有的)。类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

     1 class A:
     2     __x =1 #_A__x
     3     def __test(self): #_A__test
     4         print('from A')
     5 print(A.__dict__)  #在方法空间中查看变形的属性
     6 #print(A.__x) #用这种方法无法调用到变量,已变形
     7 print(A._A__x)  #若想强行访问,正确的调用方式
     8 a = A()  #实例化
     9 print(a.__dict__)
    10 print(a._A__x)  #对象的调用
    11 
    12 A._A__test(123)  #强制访问,类的调用
    13 a._A__test()    #强制访问,对象的调用
    14 
    15 #执行结果:
    16 {'__module__': '__main__', '_A__x': 1, '_A__test': <function A.__test at 0x000000000292C9D8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
    17 1
    18 {}
    19 1
    20 from A
    21 from A  

      2、注意:__名字,这种语法只在定义的时候才会有变形的效果,如果类或者对象已经产生了,就不会有变形效果。

      变形的过程只在类的定义时发生一次;在定义后的赋值操作,就不会变形
    #注意:__名字,这种语法只在定义的时候才会有变形的效果,如果类或者对象已经产生了,就不会有变形效果。
    #变形的过程只在类的定义时发生一次;在定义后的赋值操作,就不会变形
    #1、内部定义
    class A:
        def __init__(self):
            self.__x =1    #_A__x 定义在产生的过程中,肯定会变形
        def tell(self):   #在类内部定义的变形变量(隐藏),需要定义一个接口函数来实现访问,外部去访问这个接口进行调用
            print(self.__x) #在类内部可以直接用__名字调用,来访问到变形的属性
    a = A()
    print(a.__dict__) #在类的名称空间中查看,已变形
    #print(a.__x)  #属性已经变形,调用不到
    a.tell()
    
    #执行结果:
    {'_A__x': 1}
    1
    
    #2、外部定义
    class B:
        pass
    
    B.__x =1 #给类添加一个为__x的变量,而不是变形
    print(B.__dict__)  #在类的名称空间中查看,未变形
    print(B.__x)   #打印结果
    
    b = B()  #类实例化
    b.__x =1  #为对象添加一个为__x的变量,而不是变形
    print(b.__dict__) #查看对象的名称空间
    print(b.__x)  #打印结果
    
    #执行结果:
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None, '__x': 1}
    1
    {'__x': 1}
    1
    3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
    # 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
    # 1、正常情况下:
    class A:
        def fa(self):
            print('from A')
        def test(self):
            self.fa()
    class B(A):
        def fa(self):
            print('from B')
    b = B()
    b.test() #b.test--->B--->A 得到b.fa() 然后在对象b中找--->类B找--->父类A找
    
    # 执行结果:
    from B
    
    # 注意:时刻谨记:在定义阶段就会变形
    # 2、把fa定义成私有的,即__fa
    class A:
        def __fa(self):   #_A__fa
            print('from A')
        def test(self):  #在定义阶段就会变形
            self.__fa()   #self._A__fa
    class B(A):
        def __fa(self):   #_B__fa
            print('from B')
    b = B()  #实例化
    b.test()  #自己的对象和类名称空间没有,在父类的名称空间找到,函数已经变形。
    
    #执行结果:
    from A
  • 相关阅读:
    队列&栈//最小栈
    队列&栈//最小栈
    队列&栈//完全平方数
    队列&栈//完全平方数
    队列 & 栈//打开转盘锁
    队列 & 栈//打开转盘锁
    队列 & 栈//岛屿的个数
    深入理解计算机系统12——并发编程
    深入理解计算机系统11——网络编程
    深入理解计算机系统10——系统级I/O
  • 原文地址:https://www.cnblogs.com/zh605929205/p/6739379.html
Copyright © 2020-2023  润新知