摘要:本章主要是對上一章類的補充。對于多態(tài)的補充子類可以被看成是父類的類型,但父類不能被看成是子類的類型。仍然以類為例,動物里有哺乳動物,卵生動物,有能飛的動物和不能飛的動物,這是兩種大的分類方式。一般在中,以為結尾類的都作為接口。
《Python編程:從入門到實踐》筆記。1. 從一個類派生出所有類
本章主要是對上一章Python類的補充。
上一篇文章說道Python類的定義與繼承一般是如下形式:
class A: # 或者寫成class A(): pass class B(A): pass
其實,對于類A,它并不是算是一個真正意義上的基類,而是和Java類似,Python中所有的類最終都繼承自object類(首字母小寫,比較特殊),所以對于A的定義可以寫成如下形式:
class A(object): pass
只是通常把object給省略了。
2. 訪問限制從上一篇中我們知道,類的屬性可以被直接訪問,如果需要對訪問做一些限制,我們可以通過定義相應的方法。在Python中,對于一般的屬性,用C++或Java的話來說,它們都是公有屬性,外部可以直接訪問,比如像下面的這個name屬性:
class A: def __init__(self, name): self.name = name
但如果我們在這個屬性前面加兩個下劃線,將其變成如下形式:
class A: def __init__(self, name): self.__name = name
那么name就變成了一個私有屬性,它只能在對象的內部被訪問,如果想以如下形式訪問則會報錯:
# 代碼: class A: -- snip -- a = A("test") print(a.__name) # 結果: AttributeError: "A" object has no attribute "__name"
那是不是真的就訪問不到這個屬性了呢?說不清是有幸還是不幸,Python沒有所謂的真正的私有屬性,Python中類的所有屬性都能被訪問。Python解釋器只是將__name換了個名稱,變成了:
self._A__name
即在前面加了一個單下劃線和類名。
# 代碼: class A: -- snip -- a = A("test") print(a._A__name) # 結果: test
強烈不建議這樣訪問屬性!而且,不同版本的Python解釋器會將這樣的屬性改成不同的名字。
3. 使用裝飾器(decorator)在上一點中說到了通過方法來訪問類的屬性,這種方式一般叫做get/set方法,最后在調用時調用的是類的方法,現在我們使用Python內置的@property裝飾器來訪問類的屬性,最后在調用時是調用的屬性,實際上它是將類的方法通過裝飾器變?yōu)閷傩?。以下是通過裝飾器和通過get/set方法來訪問屬性的代碼比較:
class Teacher: def __init__(self, name): self.name = name def get_name(self): return self.name def set_name(self, name): # 可以加上一些限制 self.name = name class Student: def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, name): # 可以加上一些限制 self._name = name t = Teacher("Miss") s = Student("Boy") print(t.get_name()) print(s.name) t.set_name("Miss Lee") s.name = "Kevin"
從上述代碼也可以看出,定義的時候,兩者的代碼量區(qū)別其實不大,但是在調用的時候,明顯使用裝飾器更方便些。
4. 類中其它類型的屬性類中除了普通的屬性,以及上述的私有屬性,還有前后都有雙下劃線的屬性,例如__xxx__,它們是特殊變量,可以被直接訪問,不是私有屬性,所以一般不要起__name__,__score__這樣的屬性名,對于方法也是如此,不光有想__init__()這樣的方法,還有很多前后都有雙下劃線的方法,比如__del__(),它是類的析構函數。在以后的文章中還會介紹許多這種方法。
不光有雙下劃線的屬性,還有單下劃線的比如 _name,前單下劃線,它表示的意思是:雖然能被訪問,但請將其看做私有屬性,不要隨便訪問。
5. 對于多態(tài)的補充子類可以被看成是父類的類型,但父類不能被看成是子類的類型。比如:
# 代碼: class Animal: pass class Dog(Animal): pass a = Animal() d = Dog() print(isinstance(d, Animal)) print(isinstance(a, Dog)) # 結果: True False
也就是說,如果我們定義了這樣一個函數:
def animal_run(animal): animal.run()
它接收Animal及其子類的所有對象,只要該類的run()方法正確編寫,Python都能在解釋時正確調用相應類的run()方法,即調用方只管調用animal_run()函數,不用管類的run()方法的細節(jié),不管是現有的類還是新擴展出的子類,只要保證run()正確實現了,那么animal_run()就是正確的。這就是著名的“開閉原則”:對擴展開放,對修改封閉。
6. 靜態(tài)語言與動態(tài)語言仍以上面的animal_run()函數為例。對于像Java這樣的靜態(tài)語言,傳入的參數必須是Animal及其子類,否則就無法調用run()方法。而對于像Python這樣的動態(tài)語言,傳入的不一定要求是Animal及其子類,只要這個對象有run()方法就行了。這就是動態(tài)語言的“鴨子類型”,只要“看起來像鴨子,走起道來像鴨子”,那它就能被看做是鴨子。Python的“file-like object”就是一種“鴨子類型”,對于真正的文件對象,都有一個read()方法,用于返回文件內容。但對于其他對象,只要正確實現了read()方法,即使它不是文件對象,它也能被看做是文件。
7. 多重繼承與MixIn設計前一篇文章中的繼承是單繼承,但Python和C++一樣,支持多重繼承;Java只支持單繼承,她通過接口類來實現多重繼承的效果。首先需要搞清楚多重繼承為什么存在。仍然以Animal類為例,動物里有哺乳動物,卵生動物,有能飛的動物和不能飛的動物,這是兩種大的分類方式。如果我們要派生出一個能飛的哺乳動物(比如蝙蝠),如果按照單一繼承,可以按如下方式:
也可以先從Animal繼承出Runnable和Flyable兩個類,再繼承出哺乳類和卵生類(相當于將上圖的二三層換了位置),但從這種單繼承可以看出,如果分類增多,類的數量將呈指數級增加。故而一般采用多重繼承的方式:
class Animal: pass class Mammalia(Animal): pass class Flyable: def fly(self): print("Flying...") class Bat(Mammalia, Flyable): pass
這樣Bat類將具有Mammalia和Flyable兩個父類的所有屬性與方法。一般在Java中,以able為結尾類的都作為接口。
在設計類的繼承的時候,一般主線都是單一繼承的,像上述例子中的從Animal派生出Manmalia,但如果后續(xù)的類中要混入一些額外的功能,但這功能又不是這個子類所獨有的,比如上述的Flyable,那么就可以通過多重繼承,從Manmalia和Runnable派生出Bat類,這就是MinIn設計,Java中采用接口來實現這種設計。
為了更好的看出繼承關系,一般將Runnable和Flyable類的名字改為RunnableMixIn和FlyableMixIn,同時,還可以定義出肉食動物CarnivorousMixIn和植食動物HerbivoresMixIn,讓子類同時擁有好幾個MinIn:
class Dog(Mammalia, RunnableMixIn, CarnivorousMixIn): pass
所以在設計類時,我們應該優(yōu)先考慮通過多重繼承來組合多個MinIn,而不是直接考慮更多層次的繼承關系。
最后,本篇較多內容是根據廖雪峰老師的博客再理解而來的,感謝廖雪峰老師!
迎大家關注我的微信公眾號"代碼港" & 個人網站 www.vpointer.net ~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/41793.html
摘要:本篇內容將從鴨子類型的動態(tài)協(xié)議,逐漸過渡到使接口更明確能驗證實現是否符合規(guī)定的抽象基類。抽象基類介紹完動態(tài)實現接口后,現在開始討論抽象基類,它屬于靜態(tài)顯示地實現接口。標準庫中的抽象基類從開始,標準庫提供了抽象基類。 《流暢的Python》筆記。本篇是面向對象慣用方法的第四篇,主要討論接口。本篇內容將從鴨子類型的動態(tài)協(xié)議,逐漸過渡到使接口更明確、能驗證實現是否符合規(guī)定的抽象基類(Abst...
摘要:本篇繼續(xù)學習之路,實現更多的特殊方法以讓自定義類的行為跟真正的對象一樣。之所以要讓向量不可變,是因為我們在計算向量的哈希值時需要用到和的哈希值,如果這兩個值可變,那向量的哈希值就能隨時變化,這將不是一個可散列的對象。 《流暢的Python》筆記。本篇是面向對象慣用方法的第二篇。前一篇講的是內置對象的結構和行為,本篇則是自定義對象。本篇繼續(xù)Python學習之路20,實現更多的特殊方法以讓...
摘要:也就是說,你可以將上述代碼中的看做單元測試,而將看做測試用例。在測試類中的每一個測試方法都必須以開頭,否則將不會被認定是一個單元測試。 《Python編程:從入門到實踐》筆記。本章主要學習如何使用Python標準庫中的unittest模塊對代碼進行簡單的測試。 1. 前言 作為初學者,并非必須為你嘗試的所有項目編寫測試;但參與工作量較大的項目時,你應對自己編寫的函數和類的重要行為進行測...
摘要:也就是說,你可以將上述代碼中的看做單元測試,而將看做測試用例。在測試類中的每一個測試方法都必須以開頭,否則將不會被認定是一個單元測試。 《Python編程:從入門到實踐》筆記。本章主要學習如何使用Python標準庫中的unittest模塊對代碼進行簡單的測試。 1. 前言 作為初學者,并非必須為你嘗試的所有項目編寫測試;但參與工作量較大的項目時,你應對自己編寫的函數和類的重要行為進行測...
摘要:函數的參數作為引用時唯一支持的參數傳遞模式是共享傳參,它指函數的形參獲得實參中各個引用的副本,即形參是實參的別名。而在上面這個例子中,類的屬性實際上是形參所指向的對象所指對象,的別名。 《流暢的Python》筆記本篇是面向對象慣用方法的第一篇,一共六篇。本篇主要是一些概念性的討論,內容有:Python中的變量,對象標識,值,別名,元組的某些特性,深淺復制,引用,函數參數,垃圾回收,de...
閱讀 2186·2023-04-25 15:00
閱讀 2365·2021-11-18 13:14
閱讀 1198·2021-11-15 11:37
閱讀 3103·2021-09-24 13:55
閱讀 1240·2019-08-30 15:52
閱讀 2659·2019-08-29 12:35
閱讀 3377·2019-08-29 11:04
閱讀 1221·2019-08-26 12:13