·類(2)
@ 繼承(inheritance)
什麼是繼承:
B繼承A:A是父類(超類),B是子類(基類)。繼承可以實現程式碼重複利用,實現屬性和方法繼承。
繼承可以使子類擁有父類的屬性和方法,也可以重新定義某些屬性、重寫某些方法,即覆蓋父類原有的屬性和方法,使其獲得父類不同的功能。當然,也可以在子類中新設定屬性和方法。從技術上看,OOP裡繼承最主要的用途是實現多型,對於多型而言,最重要的是介面的繼承性(屬性和方法是否存在繼承性),這是不一定的。繼承也不是全為了程式碼的重複利用,而是為了理順關係。
對於 Python 中的繼承,前面一直在使用,那就是我們寫的類都是新式類,所有新式類都是繼承自 object 類。不要忘記,新式類的一種寫法:
class NewStyle(object): pass
這就是典型的繼承。
繼承的基本概念:
class Person: def __init__(self,name): self.name = name def get_name(self): print ("hello,{}!".format(self.name)) def setHeight(self, n): self.length = n def breast(self, n): print ("My breast is: ",n) class Boy(Person): def setHeight(self): print ("The height is:1.80m .") j = Boy(`jimmy`) j.get_name() j.setHeight() j.breast(90) 列印結果: hello,jimmy! The height is:1.80m . My breast is: 90
首先,定義了一個父類 Person,定義一個子類 Boy,Boy類的括號中是Person,這就意味著 Boy 繼承了 Person,Boy 是 Person 的子類,Person 是 Boy 的父類。那麼 Boy 就全部擁有了 Person 中的方法和屬性。
如果 Boy 裡面有一個和 Person 同樣名稱的方法,那麼就把 Person 中的同一個方法遮蓋住了,顯示的是 Boy 中的方法,這叫做方法的重寫。例項化類 Boy之後,執行例項方法 j.setHeight(),由於在類 Boy中重寫了 setHeight 方法,那麼 Person 中的那個方法就不顯作用了,在這個例項方法中執行的是類 Boy 中的方法。
雖然在類 Boy 中沒有看到 get_name() 方法,但是因為它繼承了 Person,所以 j.get_name() 就執行類 Person 中的方法。同理 j.breast(90) ,它們就好像是在類 Boy 裡面已經寫了這兩個方法一樣。既然繼承了,就可以使用。
多重繼承:
子類繼承多個父類:
class Person: def eye(self): print("two eyss") def breast(self,n): print("the breast is:",n) class Girl: age = 18 def color(self): print("the girl is white.") class HotGirl(Person,Girl): pass
在類的名字後面的括號中把所繼承的兩個類的名字寫上,HotGirl 就繼承了Person 和 Girl這兩個類。例項化類 HotGirl,既然繼承了上面的兩個類,那麼那兩個類的方法就都能夠拿過來使用。在類 Girl 中, age = 28,在對 HotGirl 例項化之後,因為繼承的原因,這個類屬性也被繼承到 HotGirl 中,因此通過例項得到它。
繼承的特點,即將父類的方法和屬性全部承接到子類中;如果子類重寫了父類的方法,就使用子類的該方法,父類的被遮蓋。
多重繼承的順序:
如果一個子類繼承了兩個父類,並且兩個父類有同樣的方法或者屬性,那麼在例項化子類後,呼叫那個方法或屬性,是屬於哪個父類的呢?造一個沒有實際意義,純粹為了解決這個問題的程式:
class K1(object): def foo(self): print("K1-foo") class K2(object): def foo(self): print("K2-foo") def bar(self): print("K2-bar") class J1(K1, K2): pass class J2(K1, K2): def bar(self): print("J2-bar") class C(J1, J2): pass print(C.__mro__) m = C() m.foo() m.bar() 列印結果: (<class `__main__.C`>, <class `__main__.J1`>, <class `__main__.J2`>, <class `__main__.K1`>, <class `__main__.K2`>, <class `object`>) K1-foo J2-bar
print C.__mro__列印出類的繼承順序。
從上面清晰看出來了,如果要執行 foo() 方法,首先看 J1,沒有,看 J2,還沒有,看 J1 裡面的 K1,有了,即 C(沒有)==>J1(沒有)==>J2(沒有)==>K1(找到了);bar() 也是按照這個順序,在 J2 中就找到了一個。
這種對繼承屬性和方法搜尋的順序稱之為“廣度優先”。新式類用以及 Python3.x 中都是按照此順序原則搜尋屬性和方法的。
但是,在舊式類中,是按照“深度優先”的順序的。因為後面讀者也基本不用舊式類,所以不舉例。
@ super函式
對於初始化函式的繼承,跟一般方法的繼承不同:
class Person: def __init__(self): self.height = 160 def about(self, name): print("{} is about {}".format(name, self.height)) class Girl(Person): def __init__(self): self.breast = 90 def about(self, name): print("{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)) cang = Girl() cang.about("wangguniang")
列印結果:
Traceback (most recent call last):
File "test1.py", line 14, in <module>
cang.about("wangguniang")
File "test1.py", line 11, in about
print("{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast))
AttributeError: `Girl` object has no attribute `height`
在上面這段程式中,類 Girl 繼承了類 Person。在類 Girl 中,初始化設定了 self.breast = 90,由於繼承了 Person,按照前面的經驗,Person 的初始化函式中的 self.height = 160 也應該被 Girl 所繼承過來。然後在重寫的 about 方法中,就是用 self.height。
例項化類 Girl,並執行 cang.about(“wangguniang”),試圖列印出一句話 wangguniang is a hot girl, she is about 160, and her bereast is 90。儲存程式,執行報錯!
資訊顯示 self.height 是不存在的。也就是說類 Girl 沒有從 Person 中繼承過來這個屬性。
在 Girl中發現, about 方法重寫了,__init__方法,也被重寫了。它跟類 Person 中的__init__重名了,也同樣是重寫了那個初始化函式。這是因為在子類中重寫了某個方法之後,父類中同樣的方法被遮蓋了。
使用super 函式:
class Person: def __init__(self): self.height = 160 def about(self, name): print("{} is about {}".format(name, self.height)) class Girl(Person): def __init__(self): super().__init__() self.breast = 90 def about(self, name): print("{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)) cang = Girl() cang.about("wangguniang") 列印結果: wangguniang is a hot girl, she is about 160, and her breast is 90
在子類中,__init__方法重寫了,為了呼叫父類同方法,使用 super(Girl, self).__init__()的方式。super 函式的引數,第一個是當前子類的類名字,第二個是 self,然後是點號,點號後面是所要呼叫的父類的方法。同樣在子類重寫的 about 方法中,也可以呼叫父類的 about 方法。
最後要提醒注意:super 函式僅僅適用於新式類。
@ 繫結方法與非繫結方法
要通過例項來呼叫類的方法(函式),經常要將類例項化。方法是類內部定義函式,只不過這個函式的第一個引數是 self。(可以認為方法是類屬性,但不是例項屬性)。必須將類例項化之後,才能通過例項呼叫該類的方法。呼叫的時候在方法後面要跟括號(括號中預設有 self 引數,可以不寫出來)。通過例項呼叫方法,稱這個方法繫結在例項上。
class Person(object): def foo(self): pass pp = Person() #例項化 print(pp.foo()) #呼叫方法
這樣就實現了方法和例項的繫結,於是通過 pp.foo() 即可呼叫該方法。
呼叫非繫結方法:
class Person: def __init__(self): self.height = 160 def about(self, name): print("{} is about {}".format(name, self.height)) class Girl(Person): def __init__(self): super().__init__() self.breast = 90 def about(self, name): print("{} is a hot girl, she is about {}, and her breast is {}".format(name, self.height, self.breast)) cang = Girl() cang.about("wangguniang") 列印結果: wangguniang is a hot girl, she is about 160, and her breast is 90
在子類 Girl 中,因為重寫了父類的__init__方法,如果要呼叫父類該方法,在上節中不得不使用 super().__init__()呼叫父類中因為子類方法重寫而被遮蔽的同名方法。
其實,非繫結方法,因為在子類中,沒有建立父類的例項,卻要是用父類的方法。對於這種非繫結方法的呼叫,還有一種方式。不過這種方式現在已經較少是用了,因為有了 super 函式。為了方便讀者看其它有關程式碼,還是要簡要說明。
例如在上面程式碼中,在類 Girl 中想呼叫父類 Person 的初始化函式,則需要在子類中,寫上這麼一行:Person.__init__(self)
這不是通過例項呼叫的,而是通過類 Person 實現了對__init__(self)的呼叫。這就是呼叫非繫結方法的用途。但是,這種方法已經被 super 函式取代,所以,如果在程式設計中遇到類似情況,推薦使用 super 函式。
@ 靜態方法和類方法
已知,類的方法第一個引數必須是 self,並且如果要呼叫類的方法,必須將通過類的例項,即方法繫結例項後才能由例項呼叫。如果不繫結,一般在繼承關係的類之間,可以用 super 函式等方法呼叫。
這裡再介紹一種方法,這種方法的呼叫方式跟上述的都不同,這就是:靜態方法和類方法。
class StaticMethod: @staticmethod def foo(): print("This is static method foo().") class ClassMethod: @classmethod def bar(cls): #類方法中要有cls引數 print("This is class method bar().") print("bar() is part of class:", cls.__name__) static_foo = StaticMethod() #例項化 static_foo.foo() #例項呼叫靜態方法 StaticMethod.foo() #通過類來呼叫靜態方法 print("********") class_bar = ClassMethod() class_bar.bar() ClassMethod.bar() 列印結果: This is static method foo(). This is static method foo(). ******** This is class method bar(). bar() is part of class: ClassMethod This is class method bar(). bar() is part of class: ClassMethod
在 Python 中:
@staticmethod表示靜態方法
@classmethod表示類方法
先看靜態方法,雖然名為靜態方法,但也是方法,所以,依然用 def 語句來定義。需要注意的是檔名後面的括號內,沒有self,這和前面定義的類中的方法是不同的,也正是因著這個不同,才給它另外取了一個名字叫做靜態方法。如果沒有 self,那麼也就無法訪問例項變數、類和例項的屬性了,因為它們都是藉助 self 來傳遞資料的。
在看類方法,同樣也具有一般方法的特點,區別也在引數上。類方法的引數也沒有 self,但是必須有 cls 這個引數。在類方法中,能夠訪問類屬性,但是不能訪問例項屬性。
簡要明確兩種方法。下面看呼叫方法。兩種方法都可以通過例項呼叫,即繫結例項。也可以通過類來呼叫,即 StaticMethod.foo() 這樣的形式,這也是區別一般方法的地方,一般方法必須用通過繫結例項呼叫。