python的類的繼承和多繼承


一、類的繼承

  •  面向對象三要素之一,繼承Inheritance
  •  class Cat(Animal)這種形式就是從父類繼承,繼承可以讓子類從父類獲取特征(屬性和方法)
  •  在面向對象的世界中,從父類繼承,就可以直接擁有父類的屬性和方法,這樣可以減少代碼,多復用,子類可以定義自己的屬性和方法
class Animal:
    def __init__(self,name):
        self._name = name
    
    def shout(self):
        print('{} shout'.format(self.__class__.__name__))
    
    @property  #屬性裝飾器
    def name(self):
        return self._name

a = Animal('monster')
a.shout()

class Cat(Animal):
    pass

cat = Cat('garfield')
cat.shout()
print(cat.name)

父類: Animal就是Cat的父類,也稱為基類,超類
子類:Cat就是Animal的子類,也稱為派生類
如果類定義時,沒有基類列表,等同於繼承自object,在python3中,object類是所有對象的根基類


1、查看繼承的特殊屬性和方法

  • __base__ : 類的基類
  • __based__ : 類的基類元組
  • __mro__ : 顯示方法查找順序,基類的元組
  • mro()方法 : 顯示方法查找順序,基類的元組
  • __subclasses__() : 類的子類列表

2、繼承中的訪問控制

  •  從父類繼承,自己沒有的就可以到父類中找
  •  私有的都是不可以訪問的,但是本質上依然是改了名稱放在這個屬性所在類的__dict_-中
  •  繼承時,公有的,子類和實例都可以隨意訪問,私有成員被隱藏了,子類和實例不可直接訪問
  •  當私有變量所在的類內的方法中可以訪問這個私有變量
  •  屬性查找屬性:實例的dict--》類的dict--》父類dict
舉例:
class Animal:
    __COUNT = 0
    HEIGHT = 0
    
    def __init__(self,age,weight,height):
        self.__COUNT += 1
        self.age = age
        self.__weight = weight
        self.HEIGHT = height
    
    def eat(self):
        print('{} eat'.format(self.__class__.__name__))
    
    def __getweight(self):
        print(self.__weight)
     
    @classmethod
    def showcount1(cls):
        print(cls.__COUNT)
    
    @classmethod
    def __showcount2(cls):
        print(cls.__COUNT)

class Cat(Animal):
    NAME = 'CAT'
    

c = Cat(3,5,15)
c.eat()
print(c.HEIGHT)  
#print(c.__COUNT)  #私有的不可訪問
#c.__showweight()  #私有的不可訪問
c.showcount1()   
#c.__showcount2()  #私有的不可訪問    
print(c.NAME)
print("{}".format(Animal.__dict__))
print("{}".format(Cat.__dict__))
print(c.__dict__)
print(c.__class__.mro())

 

3、方法的重寫、覆蓋overrride

class Animal:
    def shout(self):
        print('Animal shout')

class Cat(Animal):
    #覆蓋了父類方法
    def shout(self):
        print('miao')
        
    #覆蓋子類自身的方法,顯示調用了父類的方法
    def shout(self):
        print(super())
        print(super(Cat,self))
        super().shout()
        super(Cat,self).shout()
        
        
c = Cat()
c.shout()


4、繼承中的初始化

    class A:
        def __init__(self,a):
            self.a = a
            
    class B(A):
        def __init__(self,b,c):
            self.b = b
            self.c = c
        
        def printv(self):
            print(self.b)
            print(self.a)  # 報錯

    f = B(200,300)
    print(f.__dict__)
    print(f.__class__.__bases__)
    f.printv()

上例代碼可知:如果類B定義時聲明繼承自類A,則在類B中__bases__中是可以看到類A,但是這和是否調用類A的構造方法是兩回事
1、如果子類B沒有定義init方法,初始化的時候會自動調用基類A是init方法

class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('A init')

class B(A):
    pass

b = B()
print(b.__dict__)

2、如果子類B定義了init方法,實例的初始化不會調用父類的初始化init方法
class A:
    def __init__(self):
        self.a1 = 'a1'
        self.__a2 = 'a2'
        print('A init')

class B(A):
    def __init__(self):
        self.b1 = 'b1'
        print('B init')

b = B()
print(b.__dict__)

3、通過上面分析,子類不會調用父類init方法,這導致沒有實現繼承效果,所以在子類的__init__方法中,
應該顯式調用父類的__init__方法

class Animal:
    def __init__(self,age):
        print('Animal init')
        self.age = age
    
    def show(self):
        print(self.age)
    
class Cat(Animal):
    def __init__(self,age,weight):
        #調用父類的__init__方法順序決定着show方法的結果
        super().__init__(age)
        print('Cat init')
        self.age = age + 1
        self.weight = weight

c = Cat(10,5)
c.show()


注意、調用父類的__init__方法,出現在不同的位置,


二、多繼承

  • OCP原則:多繼承,少修改;繼承的用途:增強基類,實現多

1、多態

  •  在面向對象中,父類,子類通過繼承聯系在一起,如果可以通過一套方法,就可以實現不同的表現,就是多態
  •  一個類繼承自多個類就是多繼承,它將具有多個類的特征

2、多繼承弊端

  •  多繼承很好的模擬了世界,因為事物很少是單一繼承,但是舍棄簡單,必然引入復雜性帶來沖突
  •  如同一個孩紙繼承了來自父母雙方的特征
  •  多繼承的實現會導致編譯器設計的復雜度增加,所以現在很多語言也舍棄了類的多繼承

3、python多繼承實現

  •  多繼承帶來路徑選擇問題,究竟繼承哪個父類的特征
  •  python使用MRO(method resolution order)解決基類搜索順序問題

4、多繼承的缺點

  •  當類很多,繼承復雜的情況下,繼承路徑太多,很難說清楚什么樣的繼承路徑
  •  python語法是允許多繼承,但是python代碼是解釋執行,只有執行到的時候才發現錯誤
舉例:
    class Printable:
        def _print(self):
            print(111,self.content)

    class Document: #第三方庫,不允許修改
        def __init__(self,content):
            self.content = content

    class Word(Document): pass  #第三方庫,不允許修改
    class Pdf(Document): pass   #第三方庫,不允許修改

    class PrintableWord(Printable,Word): pass
    print(222,PrintableWord.__dict__)
    print(333,PrintableWord.mro())

    pw = PrintableWord('test string')
    pw._print()

5、用裝飾器增強

  •  用裝飾器增強一個類,把功能給類附加上去,哪個類需要,就裝飾它
  •  優點:簡單方便,在需要的地方動態增加,直接使用裝飾器
def printable(cls):
    def _print(self):
        print(self.content,'裝飾器')
    
    cls.print = _print
    return cls

class Document: #第三方庫,不允許修改
    def __init__(self,content):
        self.content = content

class Word(Document): pass  #第三方庫,不允許修改
class Pdf(Document): pass   #第三方庫,不允許修改

@printable #先繼承,后裝飾
class PrintableWord(Word): pass
print(PrintableWord.__dict__)
print(PrintableWord.mro())
pw = PrintableWord('test string')
pw.print()


6、Mixin的引用

  •  Mixin本質上就是多繼承實現的,是一種組合的設計模式
  •  在面向對象的設計中,一個復雜的類往往需要很多功能,而這些功能有來自不同的類提供,這就需要很多的類組合在一起
  •  從設計的模式的角度來說,多組合,少繼承
舉例:    
    class PrintableMixin:
        def print(self):
            print(self.content,'Mixin')
            
    def printable(cls):
        def _print(self):
            print(self.content,'裝飾器')
        
        cls.print = _print
        return cls

    class Document: #第三方庫,不允許修改
        def __init__(self,content):
            self.content = content

    class Word(Document): pass  #第三方庫,不允許修改
    class Pdf(Document): pass   #第三方庫,不允許修改
        
    class PrintableWord(PrintableMixin,Word): pass
    print(PrintableWord.__dict__)
    print(PrintableWord.mro())

    pw = PrintableWord('test string')
    pw.print()    

Mixin就是其他類混合進來,同時帶來了類的屬性和方法,這里看來Mixin類和裝飾器效果一樣,也沒有什么特別
但是Mixin是類,就可以繼承
    


7、Mixin類的使用原則

  •   Mixin類中不應該顯式的出現__init__初始化方法
  •   Mixin類通常不能獨立工作,因為它是准備混入別的類中的部分功能實現
  •   Mixin類的祖先類也應是Mixin類,使用時,Mixin類通常在繼承列表的第一個位置

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM