python 類的繼承和派生
面向對象的編程帶來的主要好處之一是代碼的複用,實現這種複用的方法之一是通過繼承機制。
python中解決代碼複用的方式:繼承和組合。
1 什麼是繼承
繼承是一種創建類的方法,在python中,一個類可以繼承來自一個或多個父類。原始類稱爲基類或超類。
class ParentClass1: #定義父類
pass
class ParentClass2: #定義父類
pass
class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
pass
查看繼承:
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
1.1 什麼時候用繼承
假如已經有幾個類,而類與類之間有共同的變量屬性和函數屬性,那就可以把這幾個變量屬性和函數屬性提取出來作爲基類的屬性。而特殊的變量屬性和函數屬性,則在本類中定義,這樣只需要繼承這個基類,就可以訪問基類的變量屬性和函數屬性。可以提高代碼的可擴展性。
1.2 繼承和抽象(先抽象再繼承)
抽象即提取類似的部分。
基類就是抽象多個類共同的屬性得到的一個類。
class Riven:
camp='Noxus'
def __init__(self,nickname,
script,
aggressivity=54,
life_value=414,
):
self.nickname = nickname
self.aggressivity = aggressivity
self.life_value = life_value
self.script=script
def attack(self,enemy):
print(self.script)
enemy.life_value -= self.aggressivity
class Garen:
camp='Demacia'
def __init__(self,nickname,
script,
aggressivity=58,
life_value=455,
):
self.nickname = nickname
self.aggressivity = aggressivity
self.life_value = life_value
self.script = script
def attack(self,enemy):
print(self.script)
enemy.life_value -= self.aggressivity
g1=Garen("德瑪西亞之力","人在塔在")
g1.camp="諾克薩斯"
r1=Riven("瑞雯","斷劍重鑄之日,騎士歸來之時")
g1.attack(r1)
print(r1.life_value)
結果:
人在塔在
356
Garen類和Riven類都有nickname、aggressivity、life_value、script四個變量屬性和attack()函數屬性,這裏可以抽象出一個Hero類,裏面有裏面包含這些屬性。
class Hero:
def __init__(self, nickname,
script,
aggressivity,
life_value,
):
self.nickname = nickname
self.aggressivity = aggressivity
self.life_value = life_value
self.script = script
def attack(self,enemy):
print("Hero.attack")
enemy.life_value -= self.aggressivity
class Riven(Hero):
camp='Noxus'
def __init__(self,nickname,script,aggressivity=54,life_value=414):
Hero.__init__(self,nickname,script,aggressivity,life_value)
def attack(self,enemy):
print(self.script)
Hero.attack(self,enemy)
class Garen(Hero):
camp='Demacia'
def __init__(self,nickname,script,aggressivity=58,life_value=455):
Hero.__init__(self, nickname, script, aggressivity, life_value)
def attack(self,enemy):
print(self.script)
Hero.attack(self, enemy)
g1=Garen("德瑪西亞之力","人在塔在")
g1.camp="諾克薩斯"
r1=Riven("瑞雯","斷劍重鑄之日,騎士歸來之時")
g1.attack(r1)
print(r1.life_value)
結果:
人在塔在
Hero.attack
356
嚴格來說,上述Hero.init(self,...),不能算作子類調用父類的方法。因爲我們如果去掉(Hero)這個繼承關係,代碼仍能得到預期的結果。
總結python中繼承的特點:
1. 在子類中,並不會自動調用基類的__init__(),需要在派生類中手動調用。
2. 在調用基類的方法時,需要加上基類的類名前綴,且需要帶上self參數變量。
3. 先在本類中查找調用的方法,找不到纔去基類中找。
1.3 繼承的好處
提高代碼的複用。
1.4 繼承的弊端
可能特殊的本類又有其他特殊的地方,又會定義一個類,其下也可能再定義類,這樣就會造成繼承的那條線越來越長,使用繼承的話,任何一點小的變化也需要重新定義一個類,很容易引起類的爆炸式增長,產生一大堆有着細微不同的子類. 所以有個“多用組合少用繼承”的原則。
2 什麼是派生
派生就是子類在繼承父類的基礎上衍生出新的屬性。子類中獨有的,父類中沒有的;或子類定義與父類重名的東西。子類也叫派生類。
3 組合
代碼複用的重要的方式除了繼承,還有組合。
組合,在一個類中以另一個類的對象作爲數據屬性,稱爲類的組合。
class Skill:
def fire(self):
print("release Fire skill")
class Riven:
camp='Noxus'
def __init__(self,nickname):
self.nickname=nickname
self.skill5=Skill().fire()#Skill類產生一個對象,並調用fire()方法,賦值給實例的skill5屬性
r1=Riven("瑞雯")
結果:
release Fire skill
4 組合和繼承的使用場景
- 繼承的方式
通過繼承建立了派生類與基類之間的關係,它是一種'是'的關係,比如白馬是馬,人是動物。
class Animal:
def walk(self):
print("Animal is walking")
def eat(self):
print("Animal is eating")
class Person(Animal):
pass
p1=Person()
p1.walk() #Animal is walking
- 組合的方式
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如老師有生日,老師教python課程。
class Teacher:
def __init__(self,name,sex,course):
self.name=name
self.sex=sex
self.course = course
class Course:
def __init__(self, name, period):
self.name = name
self.period = period
co=Course("python","7 m")
t1=Teacher("zhang","male",co)
print(t1.course.name)
結果:
python
當類之間有顯著不同,並且較小的類是較大的類所需要的組件時,用組合比較好。
5 接口和歸一化設計
什麼是接口?接口就是對外只提供抽象的方法的集合。
繼承的兩種用途:
- 繼承基類的方法,並且做出自己的改變或者擴展(代碼複用)
- 聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能。
class Interface:#定義接口Interface類來模仿接口的概念,python中沒有interface關鍵字來定義一個接口。
def read(self): #定接口函數read
pass
#raise AttributeError("read function must be implemented") #如果不重寫read函數就報錯
def write(self): #定義接口函數write
pass
#raise AttributeError("wirte function must be implemented") #如果不重寫write函數就報錯
class Txt(Interface): #文本,具體實現read和write
def read(self):
print('文本數據的讀取方法')
def write(self):
print('文本數據的讀取方法')
class Sata(Interface): #磁盤,具體實現read和write
def read(self):
print('硬盤數據的讀取方法')
def write(self):
print('硬盤數據的讀取方法')
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因爲它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
6 抽象類
6.1 什麼是抽象類
與java一樣,python也有抽象類的概念,但是需要藉助模塊實現。抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化。
抽象類與普通類的不同之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。
class Interface(metaclass=abc.ABCMeta): # 定義接口Interface類來模仿接口的概念,python中沒有interface關鍵字來定義一個接口。
@abc.abstractclassmethod
def read(self): # 定接口函數read
pass
@abc.abstractclassmethod
def write(self): # 定義接口函數write
pass
class Txt(Interface): # 文本,具體實現read和write
def read(self):
print('文本數據的讀取方法')
def write(self):
print('文本數據的讀取方法')
class Sata(Interface): # 磁盤,具體實現read和write
def read(self):
print('硬盤數據的讀取方法')
def write(self):
print('硬盤數據的讀取方法')
6.2 抽象類與接口
抽象類的本質還是類,抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計 。