zoukankan      html  css  js  c++  java
  • Python面向对象三要素-继承(Inheritance)

               Python面向对象要素-继承(Inheritance)

                                          作者:尹正杰

    版权声明:原创作品,谢绝转载!否则将追究法律责任。

    一.继承概述

    1>.基本概念

      前面我们学习了Python的面向对象三要素之一,封装。今天我们来学习一下继承(Inheritance)

      人类和猫类都继承自动物类。

      个体继承自父类,继承了父类的一部分特征,但也可以有自己的个性。

      再面向对象的世界中,从父类继承,就可以直接拥有父类的属性方法,这样可以减少代码,多复用。子类可以定义自己的属性和方法。

    2>.看一个不用继承的例子

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 
     7 class Animal:
     8     def shout(self):
     9         print("Animal shouts")
    10 
    11 
    12 class Cat:
    13     def shout(self):
    14         print("Cat shouts")
    15 
    16 
    17 a = Animal()
    18 a.shout()
    19 
    20 c = Cat()
    21 c.shout()
    22 
    23 
    24 
    25 #以上代码执行结果如下:
    26 Animal shouts
    27 Cat shouts

    3>. 使用继承的方式改良上一个不用继承的案例

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 
     7 class Animal:
     8     def __init__(self,name):
     9         self._name = name
    10 
    11     def shout(self):        #定义一个通用的吃方法
    12         print("{} shouts".format(self.__class__.__name__))
    13 
    14     @property
    15     def name(self):
    16         return self._name
    17 
    18 class Cat(Animal):
    19     pass
    20 
    21 class Dog(Animal):
    22     pass
    23 
    24 a = Animal("monster")
    25 a.shout()
    26 
    27 cat = Cat("Kitty")
    28 cat.shout()
    29 print(cat.name)
    30 
    31 dog = Dog("二哈")
    32 dog.shout()
    33 print(dog.name)
    34 
    35 
    36 #以上代码执行结果如下:
    37 Animal shouts
    38 Cat shouts
    39 Kitty
    40 Dog shouts
    41 二哈
     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 class Document:
     7     def __init__(self,content):
     8         self.content = content
     9 
    10     def print(self):     #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
    11         """
    12             基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。
    13         """
    14         raise  NotImplementedError()
    15 
    16 class Word(Document):
    17     pass
    18 
    19 class Pdf(Document):
    20     pass
    Python中抽象方法的案例

    4>.总结

      通过上例可以看出,通过继承,猫类,狗类不用写代码,直接继承了父类的属性和方法。

      继承:
        class Cat(Animal)这种形式就是从父类继承,括号中写上继承的类的列表。
        继承可以让子类从父类获取特征(属性和方法)

      父类:
        calss Animal就是Cat和Dog的父类,也称为基类,超类。

      子类:
        Cat就是Animal的子类,也成为派生类。

    二.继承定义

    1>.继承使用格式

    通过上面的案例,相比大家也可以总结出来继承的使用格式:
      class 子类名(基类1[,基类2,...])

    和C++一样,Python也支持多继承,继承也可以多级。

    2>.在Python3中,object类是所有对象的根基类

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 
     7 class A:
     8     pass
     9 
    10 #如果类定义时,没有基类列表,等同于继承自object。
    11 class A(object):
    12     pass
    13 
    14 
    15 """
    16     注意,上例在Python2中,两种写法时不同的。
    17     Python支持多继承,继承也可以多级。
    18 """

    3>.查看继承的特殊属性和方法

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 
     7 class A:
     8     pass
     9 
    10 print(A.__base__)               #类的基类。
    11 print(A.__bases__)              #类的基类元组。
    12 print(A.__mro__)                #显示方法查找顺序,基类的元组。
    13 print(A.mro())                  #同上,返回列表。
    14 print(A.__subclasses__())       #类的子类列表。
    15 
    16 
    17 
    18 #以上代码执行结果如下:
    19 <class 'object'>
    20 (<class 'object'>,)
    21 (<class '__main__.A'>, <class 'object'>)
    22 [<class '__main__.A'>, <class 'object'>]
    23 []

    三.继承中的访问控制

    1>.代码案例

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 """
     7     从父类继承,自己没有的,就可以到父类中找。
     8     私有的都是不可以访问的,但是本质上依然是改了名称放在这个属性所在类的实例"__dict__"中。
     9     知道这个新名称就可以直接找到这个隐藏的变量,这是个黑魔法技巧,慎用。
    10 """
    11 
    12 class Animal:
    13     __COUNT = 100
    14     HEIGHT = 0
    15 
    16     def __init__(self,age,weight,height):
    17         self.__COUNT += 1
    18         self.age = age
    19         self.__weight = weight
    20         self.HEIGHT = height
    21 
    22     def eat(self):
    23         print("{} eat".format(self.__class__.__name__))
    24 
    25     def __getweight(self):
    26         print(self.__weight)
    27 
    28     @classmethod
    29     def showcount1(cls):
    30         print(cls)
    31         print(cls.__dict__)
    32         print(cls.__COUNT)
    33 
    34     @classmethod
    35     def __showcount2(cls):
    36         print(cls.__COUNT)
    37 
    38     def showcount3(self):
    39         print(self.__COUNT)
    40 
    41 class Cat(Animal):
    42     NAME = 'CAT'
    43     __COUNT = 200
    44 
    45 
    46 c = Cat(3,5,15)
    47 c.eat()
    48 print(c.HEIGHT)
    49 # print(c.__COUNT)          #不能访问,因为它属于私有变量,该私有变量python内部做了处理,变量名称被更改。
    50 
    51 # print(c.__getweight())    #同上,这是私有方法,该私有方法python内部做了处理,变量名称被更改。
    52 
    53 c.showcount1()
    54 
    55 # c.__showcount2()            #无法直接访问父类的私有方法,如果你非要访问的话,可以使用“c._Animal__showcount2()”
    56 
    57 c.showcount3()
    58 
    59 print(c._Cat__COUNT)
    60 
    61 print(c._Animal__COUNT)
    62 
    63 print(c.NAME)
    64 
    65 print("{}".format(Animal.__dict__))
    66 print("{}".format(Cat.__dict__))
    67 print(c.__dict__)
    68 print(c.__class__.mro())
    69 
    70 
    71 
    72 #以上代码执行结果如下:
    73  Cat eat
    74 15
    75 <class '__main__.Cat'>
    76 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
    77 100
    78 101
    79 200
    80 101
    81 CAT
    82 {'__module__': '__main__', '_Animal__COUNT': 100, 'HEIGHT': 0, '__init__': <function Animal.__init__ at 0x000001F6AB694438>, 'eat': <function Animal.eat at 0x000001F6AB6944C8>, '_Animal__getweight': <function Animal.__getweight at 0x000001F6AB694558>, 'showcount1': <classmethod object at 0x000001F6AB6BA388>, '_Animal__showcount2': <classmethod object at 0x000001F6AB6BA0C8>, 'showcount3': <function Animal.showcount3 at 0x000001F6AB6949D8>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
    83 {'__module__': '__main__', 'NAME': 'CAT', '_Cat__COUNT': 200, '__doc__': None}
    84 {'_Animal__COUNT': 101, 'age': 3, '_Animal__weight': 5, 'HEIGHT': 15}
    85 [<class '__main__.Cat'>, <class '__main__.Animal'>, <class 'object'>]

    2>.总结

      继承时,公有的,子类和实例都可以随意访问;私有成员被隐藏,子类和实例不可直接访问,但私有变量所在的类内的方法中可以访问这个私有变量。

      Python通过自己一套实现,实现和其它语言一样的面向对象的继承机制。

      实例属性查找顺序:
        实例的 "__dict__" ===> "类__dict__" ===> "父类__dict"
        如果搜索这些地方后没有找到就会抛出异常,先找到就立即返回了。

    四.方法的重写,覆盖override

    1>.super()可以访问到父类的类属性

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 class Animal:
     7     def shout(self):
     8         print("Animal shouts")
     9 
    10 class Cat(Animal):
    11     # def shout(self):            #覆盖了父类方法
    12     #     print("miao")
    13 
    14     def shout(self):              #覆盖了自身的方法,显式调用了父类的方法
    15         print(super())
    16         print(super(Cat,self))
    17         print(super(self.__class__,self))
    18 
    19         super().shout()
    20         super(Cat,self).shout()     #等价于super()
    21         self.__class__.__base__.shout(self)
    22 
    23 a = Animal()
    24 a.shout()
    25 
    26 c = Cat()
    27 c.shout()
    28 
    29 print(a.__dict__)
    30 print(c.__dict__)
    31 print(Animal.__dict__)
    32 print(Cat.__dict__)
    33 
    34 
    35 
    36 
    37 #以上代码执行结果如下:
    38 Animal shouts
    39 <super: <class 'Cat'>, <Cat object>>
    40 <super: <class 'Cat'>, <Cat object>>
    41 <super: <class 'Cat'>, <Cat object>>
    42 Animal shouts
    43 Animal shouts
    44 Animal shouts
    45 {}
    46 {}
    47 {'__module__': '__main__', 'shout': <function Animal.shout at 0x000001DD6FB85678>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
    48 {'__module__': '__main__', 'shout': <function Cat.shout at 0x000001DD6FB851F8>, '__doc__': None}

    2>.类方法和静态方法覆盖

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 """
     7     这些方法都可以覆盖,原理都一样,属性字典的搜索顺序。
     8 """
     9 
    10 class Animal:
    11 
    12     @classmethod
    13     def class_method(cls):
    14         print("class_method_animal")
    15 
    16     @staticmethod
    17     def static_method():
    18         print("static_method_animal")
    19 
    20 class Cat(Animal):
    21 
    22     @classmethod
    23     def class_method(cls):
    24         print("class_method_cat")
    25 
    26     @staticmethod
    27     def static_method():
    28         print("static_method_cat")
    29 
    30 c = Cat()
    31 c.class_method()
    32 c.static_method()
    33 
    34 print(Cat.__dict__)
    35 print(Animal.__dict__)
    36 
    37 Cat.static_method()
    38 Animal.static_method()
    39 
    40 
    41 
    42 #以上代码执行结果如下:
    43 class_method_cat
    44 static_method_cat
    45 {'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B18317588>, 'static_method': <staticmethod object at 0x0000019B183175C8>, '__doc__': None}
    46 {'__module__': '__main__', 'class_method': <classmethod object at 0x0000019B183174C8>, 'static_method': <staticmethod object at 0x0000019B18317508>, '__dict__': <attribute '__dict__' of 'Animal' objects>, '__weakref__': <attribute '__weakref__' of 'Animal' objects>, '__doc__': None}
    47 static_method_cat
    48 static_method_animal

    五.继承时使用初始化

    1>.手动调用父类的构造方法

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 
     7 class A:
     8     def __init__(self,a,d = 10):
     9         self.a = a
    10         self.__d = d
    11 
    12 class B:
    13     def __init__(self,b,c):
    14         A.__init__(self,b + c,b - c)            #我们调用了A的构造方法,那么就可以使用它的属性啦。
    15         self.b = b
    16         self.c = c
    17 
    18     def printv(self):
    19         print(self.b)
    20         print(self.a)
    21 
    22 
    23 f = B(200,300)
    24 print(f.__dict__)
    25 print(f.__class__.__bases__)
    26 f.printv()
    27 
    28 
    29 
    30 #以上代码执行结果如下:
    31 {'a': 500, '_A__d': -100, 'b': 200, 'c': 300}
    32 (<class 'object'>,)
    33 200
    34 500

    2>.自动调用父类的构造方法

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 class Animal:
     7     def __init__(self,age):
     8         print("init in Animal")
     9         self.age = age
    10 
    11     def show(self):
    12         print(self.age)
    13 
    14 class Cat(Animal):
    15     def __init__(self,age,weight):
    16         #调用父类的__init__方法的顺序有时决定着show方法的结果
    17         super().__init__(age)
    18         print("init in Cat")
    19         self.age = age + 1
    20         self.weight = weight
    21         # super().__init__(age)     #调用父类的方法其实也可以不用放在第一行,在Java语言中必须放在构造方法的首行。
    22 
    23 c = Cat(10,5)
    24 c.show()
    25 
    26 
    27 
    28 #以上代码执行结果如下:
    29 init in Animal
    30 init in Cat
    31 11

    3>.属性的继承说明

      一个原则,自己的私有属性,就该自己的方法读取和修改,不要借助其它类的方法,即使是父类或者派生类的方法。

    六.多继承

    1>.Python不同版本的类概述

      Python2.2之前类时没有共同祖先的,之后,引入object类,它时所有类的共同祖先类object。

      Python2.7.X中为了兼容,分为古典类(旧式类)和新式类。

      Python3中全部都是新式类。

      新式类都是继承自object,新式类可以使用super。

    2>.Python多继承实现

      在面向对象这种,父类,子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态。一个类继承自多个类就是多继承,它将具有多个类的特征。
    
       多继承毕竟会带来路径选择问题,究竟继承哪个父类的特征呢?如上图所示,左图是多继承(菱形继承),右图是单一继承。
    
      Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题。
    
      历史原因,MRO有三个搜素算法:
        经典算法,按照定义从左到右,深度优先策略。2.2版本之前左图的MRO是MyClass->D->B->A->C->A
        新式类算法,是经典算法的升级,深度优先,重复的只保留最后一个。2.2版本左图的MRO是MyClass->D->B->C->A->object
        C3算法,在类被创建出来的时候,就计算一个MRO有序列表。2.3之后,Python3唯一支持的算法左图中的MRO是MyClass->D->B->C-A->object的列表。C3算法解决多继承的二义性。
      
      经典算法有很大的问题,如果C中有覆盖A的方法,也不会访问到它,因为先访问A的(深度优先)。
    
      新式类算法,依然采用了深度优先,解决重复问题,但是同经典算法一样,没有解决继承的单调性。
      
      C3算法,解决了继承的单调性,它阻止创建之前产生二义性的代码。求得的MRO本质是为了线性化,且确定了顺序。
    
      单调性:假设有A,B,C三个类,C的mro是[C,A,B],那么C的子类的mro中,A,B的顺序一致就是单调的。

    3>.多继承的缺点

      多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。

      如同一个孩子继承了来自父母双方的特征。那么到底眼睛像爸爸还是妈妈呢?孩子究竟该像谁多一点呢?

      多继承的实现会导致编译器设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承。

      C++支持多继承;Java舍弃了多继承。

      Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法的声明,继承者必须实现这些方法,就具有了这些能力。就能干什么。

      多继承可能会带来二义性,例如,猫和狗都继承自动物类,现在如果一个多继承了猫和狗类,猫和狗都有shout方法,子类究竟继承谁的shout呢?解决方案:实现多继承的语言,要解决二义性,深度优先或者广度优先。

      当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。

      Python语法时允许多继承,但Python代码时解释执行,只是执行到的时候才发现错误。

      团队协作开发,如果引入多继承,那代码很可能不可控。

      不管编程语言是否支持多继承,都应当避免多继承。

      Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。

    七.Mimin类

    1>.单继承存在的弊端案例

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 class Document:
     7     def __init__(self,content):
     8         self.content = content
     9 
    10     def print(self):     #基类中只定义,不实现的方法,称为“抽象方法”。在python中,如果采用这种方式定义的抽象方法,子类可以不实现,知道子类使用该方法的时候才报错。
    11         """
    12             基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写。
    13         """
    14         raise  NotImplementedError()
    15 
    16 class Word(Document):
    17     pass
    18 
    19 class Pdf(Document):
    20     pass
    21 
    22 
    23 """
    24     抛出问题:
    25         从上面的案例可以看出print算是一种能力(打印功能),不是所有的Document的子类都需要的,所以从这个角度触发,上面的基类Document设计有点问题。
    26         
    27     解决思路:
    28         如果在现有子类Word或Pdf上直接增加,虽然可以,却违反了OCP的原则(多用“继承”,少修改),所以可以继承后增加打印功能。
    29         
    30         在这个时候发现,为了增加一种能力,就要增加一次继承,类可能太多了,继承的方式不是很好了。
    31         
    32         功能太多,A类需要某几样功能,B类需要另几样功能,他们需要的是多个功能的自由组合,继承实现很繁琐。
    33         
    34         我们可以引入类装饰器来解决问题,装饰器的有点在于:
    35             简单方便,在需要的地方动态增加,直接使用装饰器
    36             可以为类灵活的增加功能。
    37         
    38         但是类装饰器不可继承,因此我们引入Mixin方案,Mixin就是其它类混合进来,同时带来了类的属性和方法。
    39         
    40         Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。
    41 """

    2>.Mixin案例展示

     1 #!/usr/bin/env python
     2 #_*_conding:utf-8_*_
     3 #@author :yinzhengjie
     4 #blog:http://www.cnblogs.com/yinzhengjie
     5 
     6 class Document:             #假设该类为第三方库,不允许修改
     7     def __init__(self,content):
     8         self.content = content
     9 
    10 
    11 class Word(Document):       #假设该类为第三方库,不允许修改
    12     pass
    13 
    14 class Pdf(Document):        #假设该类为第三方库,不允许修改
    15     pass
    16 
    17 
    18 
    19 def printable(cls):                 #定义类装饰器
    20     def _print(self):
    21         print(self.content,"装饰器")
    22     cls.print = _print
    23     return cls
    24 
    25 @printable
    26 class PrintablePdf(Word):           #使用类装饰器和Mixin进行使用上的对比
    27     pass
    28 
    29 print(PrintablePdf.__dict__)
    30 print(PrintablePdf.mro())
    31 
    32 
    33 class PrintableMimin:                    #Mixin就是其它类混合进来,同时带来了类的属性和方法,这里看来和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。
    34     def print(self):
    35         print(self.content,"Mixin")
    36 
    37 class PrintableWord(PrintableMimin,Word):       #Mixin类本质上就是多继承实现的。Mixin体现的是一种组合的设计模式。
    38     pass
    39 
    40 print(PrintableWord.__dict__)
    41 print(PrintableWord.mro())
    42 
    43 
    44 pw = PrintableWord("test string")
    45 pw.print()
    46 
    47 class SuperPrintableMixin(PrintableMimin):
    48     def print(self):
    49         print("{0} 打印增强前 {0}".format("*" * 20))
    50         super().print()
    51         print("{0} 打印增强后 {0}".format("*" * 20))
    52 
    53 
    54 class SuperPrintablePdf(SuperPrintableMixin,Pdf):
    55     pass
    56 
    57 print(SuperPrintablePdf.__dict__)
    58 print(SuperPrintablePdf.mro())
    59 
    60 spp = SuperPrintablePdf("super print pdf")
    61 spp.print()
    62 
    63 
    64 
    65 #以上代码执行结果如下:
    66 {'__module__': '__main__', '__doc__': None, 'print': <function printable.<locals>._print at 0x0000016E8C6459D8>}
    67 [<class '__main__.PrintablePdf'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
    68 {'__module__': '__main__', '__doc__': None}
    69 [<class '__main__.PrintableWord'>, <class '__main__.PrintableMimin'>, <class '__main__.Word'>, <class '__main__.Document'>, <class 'object'>]
    70 test string Mixin
    71 {'__module__': '__main__', '__doc__': None}
    72 [<class '__main__.SuperPrintablePdf'>, <class '__main__.SuperPrintableMixin'>, <class '__main__.PrintableMimin'>, <class '__main__.Pdf'>, <class '__main__.Document'>, <class 'object'>]
    73 ******************** 打印增强前 ********************
    74 super print pdf Mixin
    75 ******************** 打印增强后 ********************

    3>.Mixin类的使用原则

      在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自抓不同的类提供,这就需要很多的类组合在一起。

      从设计模式的角度来说,应该多组合,少继承。

      Mixin类的使用原则:
        Mixin类中不应该显式的出现__init__初始化方法。
        Mixin类通常不能独立工作,因为它式准备混入别的类中的部分功能实现。
        Mixin类的祖先类也应该式Mixin类。

      使用时,Mixin类通常在继承列表的第一个位置,例如上来中的"class PrintableWord(PrintableMixin,Word):pass"

      Mixin类和装饰器:
        这两种方式都可以使用,看个人喜好。
        如果还需要继承就得使用Mixin类的方式。
  • 相关阅读:
    基于小程序开发的藏书馆
    picker(级联)组件及组件封装经验
    秒杀组件开发-可实现多种倒计时功能
    async/await 与 generator、co 的对比
    nodejs项目总结
    小程序开发小结-线下服务器域名部署等
    性能提速:debounce(防抖)、throttle(节流/限频)
    vuex数据管理-数据模块化
    vue 项目其他规范
    vue路由管理-保留滚动位置功能、按需加载模块名自定义
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/11173836.html
Copyright © 2011-2022 走看看