本文從代碼複用的角度一步一步演示如何從python普通代碼進化到面向對象,並經過代碼去解釋一些面向對象的理論。因此,本文前面的內容都是非面向對象的語法實現方式,只有在最結尾纔給出了面向對象的簡單語法介紹。各位道兄不妨一看,若是留下點筆墨指導,本人感激涕零。java
3種動物牛Cow、羊Sheep、馬Horse發出的聲音各不相同,因而在同一個目錄下創建三個模塊文件:python
$ tree . . |-- cow.py |-- horse.py `-- sheep.py
三個模塊文件的內容都只定義了各自的speak()函數:c++
# cow.py def speak(): print("a cow goes moooo!") # sheep.py def speak(): print("a sheep goes baaaah!") # horse.py def speak(): print("a horse goes neigh!")
而後當前目錄下在建立一個程序文件main.py,導入這三個模塊文件,分別調用這三種動物的speak()函數,它們將發出不一樣聲音:編程
# main.py import cow,sheep,horse cow.speak() sheep.speak() horse.speak()
上面的cow.py、sheep.py和horse.py中,都是speak()函數,不一樣的是函數內容,確切地說是函數內容中print()輸出的部分不一樣,它們輸出的結構是a 動物名 goes 叫聲!
。因而爲了讓代碼更具共性,或者說複用性更高,能夠將各模塊文件中的動物名和叫聲都變得通用化。c#
目前來講,有兩種最基本的方式可讓一段代碼變得更共性、共通用化:使用參數或變量、使用額外的輔助函數。固然,除此以外還有更多的方法,但目前來講這兩種是最基本的,也是最容易理解的。ruby
首先讓動物名變得共性化。可讓speak()中的動物名使用一個參數來替代。例如名爲self的參數變量(之因此使用self,是由於在面向對象中它有特殊含義,後文解釋),因而修改這三個模塊文件:ide
# cow.py def speak(self): print("a %s goes moooo!" % (self)) # sheep.py def speak(self): print("a %s goes baaaah!" % (self)) # horse.py def speak(self): print("a %s goes neigh!" %(self))
它們如今在動物名上和參數名上已經徹底相同,須要調用它們時,只需在函數調用處爲他們傳遞不一樣的動物名便可。例如,在main.py中:函數
import cow,sheep,horse cow.speak("cow") sheep.speak("sheep") horse.speak("horse")
除了參數(變量),還能夠定義額外的函數來上面的代碼變得更具共性。例如,這三種動物的叫聲,能夠額外定義一個sound()函數描述它們。因而在前面的基礎上繼續修改這三個模塊文件:工具
# cow.py def speak(self): print("a %s goes %s!" % (self,sound())) def sound(): return "moooo" # sheep.py def speak(self): print("a %s goes %s!" % (self,sound())) def sound(): return "baaaah" # horse.py def speak(self): print("a %s goes %s!" % (self,sound())) def sound(): return "neigh"
在main.py中,仍然可使用以前的方式對這3個speak()進行調用:this
import cow,sheep,horse cow.speak("cow") sheep.speak("sheep") horse.speak("horse")
如今,這3個模塊文件的speak()已經完徹底全地共性化了。
所謂的類,就像是一個模板;所謂對象,就像是經過模板生成的具體的事物。類通常具備比較大的共性,對象通常是具體的,帶有本身的特性。
類與對象的關係,例如人類和人,鳥類和麻雀,交通工具和自行車。其中人類、鳥類、交通工具類都是一種類型稱呼,它們中的任何一種都具備像模板同樣的共性。例如人類的共性是能說話、有感情、雙腳走路、能思考等等,而根據這我的類模板生成一我的,這個具體的人是人類的實例,是一我的類對象,每個具體的人都有本身的說話方式、感情模式、性格、走路方式、思考能力等等。
類與類的關係。有的類的範疇太大,模板太抽象,它們能夠稍微細化一點,例如人類能夠劃分爲男性人類和女性人類,交通工具類能夠劃分爲燒油的、電動的、腳踏的。一個大類按照不一樣的種類劃分,能夠獲得不一樣標準的小類。不管如何劃分,小類老是根據大類的模板生成的,具備大類的共性,又具備本身的個性。
在面向對象中,小類和大類之間的關係稱之爲繼承,小類稱之爲子類,大類稱之爲父類。
類具備屬性,屬性通常包括兩類:像名詞同樣的屬性,像動詞同樣的行爲。例如,人類有父母(parent),parent就是名詞,人類能吃飯(eat),eat這種行爲就是動詞。鳥類能飛(fly),fly的行爲就是動詞,鳥類有翅膀(wing),wing就是名詞。對於面向對象來講,名詞就是變量,動詞行爲就是方法(也就是子程序)。一般,變量和方法都成爲類的屬性。
當子類繼承了父類以後,父類有的屬性,子類能夠直接擁有。由於子類通常具備本身的個性,因此子類能夠定義本身的屬性,甚至修改從父類那裏繼承來的屬性。例如,人類中定義的eat屬性是一種很是抽象的、共性很是強的動詞行爲,若是女性人類繼承人類,那麼女性人類的eat()能夠直接使用人類中的eat,也能夠定義本身的eat(好比淑女地吃)覆蓋從人類那裏繼承來的eat(沒有形容詞的吃),女性人類還能夠定義人類中沒有定義的跳舞(dance)行爲,這是女性人類的特性。子類方法覆蓋父類方法,稱之爲方法的重寫(override),子類定義父類中沒有的方法,稱爲方法的擴展(extend)。
當經過類構造出對象後,對象是類的實例,是類的具體化,對象將也具有類的屬性,且對象的屬性都有各自的值。例如,student類具備成績、班級等屬性,對於一個實際的學生A對象來講,他有成績屬性,且這個成績具備值,好比89分,班級也同樣,好比2班,此外,學生B也有本身的成績和班級以及對應的值。也就是說,根據類模板生成對象後,對象的各個屬性都屬於本身,不一樣對象的屬性互不影響。
不管是對象與類仍是子類與父類,它們的關係均可以用一種"is a"來描述,例如"自行車 is a 交通工具"(對象與類的關係)、"筆記本 is a 計算機"(子類與父類的關係)。
回到上面的3個模塊文件。它們具備共性的speak()和sound(),儘管sound()的返回內容各不相同,但至少函數名sound是相同的。
能夠將這3個文件中共性的內容抽取到同一個模塊文件中,假設放進animal.py的文件中。animal.py文件的內容爲(但這是錯誤的代碼,稍後修改):
def speak(self): print("a %s goes %s!" % (self,sound())) def sound(): pass
而後修改cow.py、sheep.py和horse.py,使它們"繼承"animal.py。
# cow.py import animal def sound(): return "moooo" # sheep.py import animal def sound(): return "baaaah" # horse.py import animal def sound(): return "neigh"
如今,這三個模塊文件都沒有了speak(),由於它們都借用它們的"父類"animal中的speak()。
這表示horse、cow和sheep"繼承"了animal,前三者爲"子類",後者爲"父類"。
但注意,這裏不是真正的繼承,由於python不支持非class對象的繼承,因此無法經過非面向對象語法演示繼承。但至少從代碼複用的角度上來講,它和繼承的功能是相似的。
另外注意,前面animal.py文件是錯誤的,由於它的speak()函數中調用了sound()函數,但sound()函數在animal.py中是一個沒任何用處的函數,僅僅只是表明這個animal具備sound()功能(表示類的一個屬性)。而咱們真正須要的sound()是能夠調用cow、horse、sheep中的sound(),而不是animal自身的sound()。
因此,在沒有使用面向對象語法的狀況下,改寫一下animal.py文件,導入cow、horse、sheep,使得能夠在"父類"的speak()中調用各個"子類"的sound()。再次說明,這裏只是爲了演示,這種編程方式是不規範的,在真正的面向對象語法中根本無需這些操做。
如下是修改後的animal.py文件:
import cow,horse,sheep def speak(self): print( "a %s goes %s!" % (self, eval(self + ".sound()")) ) def sound(): pass
上面使用eval函數,由於python不支持普通的變量名做爲模塊名來調用模塊的屬性sound(),因此使用eval先解析成cow或horse或sheep,再調用各自的sound()函數。若是不懂eval()的功能,可無視它。只需知道這是爲了實現self.sound()
來調用self所對應變量的sound()函數。
如今,在main.py中,使用下面的代碼來調用speak(),獲得的結果和前面是同樣的。
import cow,sheep,horse cow.animal.speak("cow") sheep.animal.speak("sheep") horse.animal.speak("horse")
因爲不是真正的"繼承",因此這裏只能經過模塊的方式添加一層animal.
來調用speak()。
雖然上面的代碼變得"人不人鬼不鬼"(由於沒有使用面向對象的語法),但面向對象的基本目標達到了:共性的代碼所有抽取出去,實現最大程度的代碼複用。
在python的面向對象語法中,將會常常看見self
這個字眼。其實不只python,各類動態類型的、支持面向對象的語言都使用self,例如perl、ruby也是如此。可是,self是約定俗成的詞,並不是是強制的,能夠將self換成其它任何字符,這並不會出現語法錯誤。
實際上,對於靜態面嚮對象語言來講,用的更多的多是this,好比java、c#、c++都使用this來表示實例對象自身。
那麼self究竟是什麼東西?
在前文,爲了將cow、sheep和horse模塊中speak()函數中的動物名稱變得共性,添加了一個self參數。以前的那段代碼以下:
# cow.py def speak(self): print("a %s goes moooo!" % (self)) # sheep.py def speak(self): print("a %s goes baaaah!" % (self)) # horse.py def speak(self): print("a %s goes neigh!" %(self))
當調用這三個函數時,分別傳遞各自的動物名做爲參數:
import cow,sheep,horse cow.speak("cow") sheep.speak("sheep") horse.speak("horse")
因此,對於cow來講,self是名爲"cow"的動物,對於sheep來講,self是名爲"sheep"的動物,對於horse來講,self是名爲"horse"的動物。
也就是說,self是各類動物對象,cow.speak()時是cow,sheep.speak()時是sheep,horse.speak()時是horse。這裏的模塊名變量和speak()的參數是一致的,這是我故意設計成這樣的,由於面向對象語法中默認的行爲和這是徹底同樣的,僅僅只是由於語法不一樣而寫法不一樣。
簡而言之,self是各個動物對象自身。
後來將cow、sheep和horse的speak()函數抽取到了animal中,仍然使用self做爲speak()的參數。
如下是animal.py文件中的speak()函數:
def speak(self): print( "a %s goes %s!" % (self, eval(self + ".sound()")) )
當使用下面的方式去調用它時:
cow.animal.speak("cow") sheep.animal.speak("sheep") horse.animal.speak("horse")
self是cow、是sheep、是horse,而不是animal。前面說了,在真正的面向對象語法中,中間的這一層animal是被省略的,這裏之因此加上一層animal,徹底是由於python的非面向對象語法中沒辦法實現繼承。
當真正使用面向對象語法的時候,self將表示實例對象自身。例如student類有name屬性,當根據此類建立一個stuA對象,並使用self.name
時,表示stuA.name
,換句話說,self是stuA這個對象自身,self.name
是stuA對象自身的屬性name,和另外一個學生對象的stuB.name
無關。
前面的animal.py中定義了一個空代碼體的sound()函數,在cow、sheep和horse中定義了屬於本身叫聲的sound()函數。這其實就是方法的重寫(方法就是函數,只是在面向對象中稱爲方法):父類定義了某個方法,子類修改和父類同名的方法。
例如,新添加一個類mouse,重寫animal的speak()方法,mouse的speak()方法中會叫兩聲,而不是其它動物同樣只有一聲。假設mouse類定義在mouse.py文件中,代碼以下:
import animal def speak(self): animal.speak(self) print(sound()) def sound(): return "jijiji"
這裏重寫了父類animal的speak(),並在mouse.speak()中調用了父類animal.speak(),再次基礎上還叫了一聲。
爲了讓這段代碼運行,須要在animal.py中導入mouse,但在真正面向對象語法中是不須要的,緣由前面說了。
# animal.py import cow,horse,sheep,mouse def speak(self): print( "a %s goes %s!" % (self, eval(self + ".sound()")) ) def sound(): pass
而後在main.py中調用mouse.speak()便可:
import cow,sheep,horse,mouse cow.animal.speak("cow") sheep.animal.speak("sheep") horse.animal.speak("horse") mouse.speak("mouse")
按照"里氏替換原則":子類重寫父類方法時,應該擴展父類的方法行爲,而不是直接否認父類的方法代碼並修改父類方法的代碼。這是一種編程原則,並不是強制,可是經驗所在,咱們應當參考甚至儘可能遵循這些偉人提出的原則。
正如上面的mouse,speak()是在父類的speak()上擴展的。若是將mouse.speak()改成以下代碼,則不符合里氏替換原則:
import animal def speak(self): print(sound()) print(sound()) def sound(): return "jijiji"
並不是必定要遵循里氏替換原則,應該根據實際場景去考慮。好比上面的sound()方法,父類的sound()是一個空方法,僅僅只是聲明爲類的屬性而存在。子類能夠隨意根據本身的類特性去定製sound()。
再舉一個擴展父類方法的例子。在父類中定義了一個clean()方法,用於清理、回收父類的一些信息。子類中也重寫一個clean()方法,但這時應當確保子類的clean()中包含了調用父類的clean()方法,再定義屬於子類獨有的應當清理的一些信息。這就是父類方法的擴展,而不是父類方法的直接否認。由於子類並不知道父類的clean()會清理哪些信息,若是徹底略過父類clean(),極可能本該被父類clean()清理的東西,子類沒有去清理。
前面的全部內容都只是爲了從代碼複用的角度去演示如何從普通編程方式演變成面向對象編程。如今,簡單介紹python面向對象編程的語法,實現前文的animal、horse、cow和sheep,由此來和前文的推演作個比較。關於面向對象,更多內容在後面的文章會介紹。
使用class關鍵字定義類,就像定義函數同樣。這裏定義4個類,父類animal,子類cow、sheep、horse,子類繼承父類。它們分別保存到animal.py、cow.py、sheep.py和horse.py文件中。
animal.py文件:
# 定義Animal類 class Animal(): def speak(self): print( "a %s goes %s!" % (self, self.sound()) ) def sound(self): pass
cow.py文件:
import animal # 定義Cow類,繼承自Animal class Cow(animal.Animal): def sound(self): return "moooo"
sheep.py文件:
import animal # 定義Sheep類,繼承自Animal class Sheep(animal.Animal): def sound(self): return "baaaah"
horse.py文件:
import animal # 定義Horse類,繼承自Animal class Horse(animal.Animal): def sound(self): return "neigh"
在main.py文件中生成這3個子類的實例,並經過實例對象去調用定義在父類的speak()方法:
import cow,horse,sheep # 生成這3個子類的實例對象 cowA = cow.Cow() sheepA = sheep.Sheep() horseA = horse.Horse() # 經過實例對象去調用speak()方法 cowA.speak() sheepA.speak() horseA.speak()
輸出結果:
a <cow.Cow object at 0x03341BD0> goes moooo! a <sheep.Sheep object at 0x03341BF0> goes baaaah! a <horse.Horse object at 0x03341F50> goes neigh!
輸出結果和想象中不同,先別管結果。至少若是把<xxx>
換成對應的實例對象名稱,就和前文的效果同樣了。這個稍後再改。
先看語法。
使用class關鍵字聲明類,類名通常首字母大寫。若是要繼承某個類,在類名的括號中指定便可,例如class Cow(Animal)
。
由於Cow、Horse、Sheep類繼承了Animal類,因此即便這3個子類沒有定義speak()方法,也將擁有(繼承)父類Animal的speak()方法。
經過調用類,能夠建立這個類的實例對象。例如上面cowA=cow.Cow()
,表示建立一個Cow()的對象,這個對象在內存中,賦值給了cowA變量。也便是說cowA引用了這個對象,是這個對象的惟一標識符。注意,cowA是變量,由於引用對象,因此能夠稱爲對象變量。
當調用cowA.speak()
時,首先查找speak()方法,由於沒有定義在Cow類中,因而查找父類Animal,發現有speak()方法,因而調用父類的speak()方法。調用時,python會自動將cowA這個對象做爲speak()的第一個參數,它將傳遞給Animal類中speak()的self參數,因此此時self表示cowA這個對象,self.sound()
表示cowA.sound()
,因爲Cow類中定義了sound(),因此直接調用Cow類的sound(),而不會調用Animal中的sound()。
和前面的推演代碼複用的過程比較一下,不難發現面向對象的語法要輕便不少,它將不少過程自動化了。
如今還有一個問題,上面的代碼輸出結果不是咱們想要的。見下文。
爲了讓speak()輸出對象名(如對象變量名cowA),這並不是一件簡單的事。
在python中,變量都是保存對象的,變量和數據對象之間是相互映射的,只要引用變量就會獲得它的映射目標。若是這個對象具備__name__
屬性,則直接引用該屬性便可獲取該變量的名稱,很簡單。
可是不少對象並無__name__
屬性,好比自定義的類的對象實例,這時想要獲取類的對象變量名,實非易事。有兩個內置函數能夠考慮:globals()函數和locals()函數,它們返回當前的全局變量和本地變量的字典。遍歷它們並對字典的value和給定變量進行比較,便可獲取想要的變量名key。
但若是跨文件了,例如Animal類在一個文件,Cow類在一個文件,建立對象的代碼又在另外一個文件,它們的做用域都是各自獨立的,想要在Animal類的方法speak()中獲取Cow類的對象變量名cowA,python應該是沒辦法實現的(perl支持,且實現很是簡單)。
因此,只能使用另外一種標識對象的方法:爲類添加屬性,例如name屬性,而後在speak()中引用對象的這個name屬性便可。
修改animal.py文件以下:
class Animal(): def speak(self,name): self.name = name print( "a %s goes %s!" % (self.name, self.sound()) ) def sound(self): pass
而後,在main.py中調用speak()的時候,傳遞name參數便可:
import cow,horse,sheep # 生成這3個子類的實例對象 cowA = cow.Cow() sheepA = sheep.Sheep() horseA = horse.Horse() # 經過實例對象去調用speak()方法 cowA.speak("cowA") sheepA.speak("sheepA") horseA.speak("horseA")
輸出結果:
a cowA goes moooo! a sheepA goes baaaah! a horseA goes neigh!
這正是期待的結果。
上面是在speak()方法中經過self.name = name
的方式設置對象horseA的name屬性。通常來講,對於那些對象剛建立就須要具有的屬性,應當放在構造方法中進行設置。
構造方法是指從類構造對象時自動調用的方法,是對象的初始化方法。python的構造方法名爲__init__()
。如下是在構造方法中設置name屬性的代碼:
class Animal(): def __init__(self,name): self.name = name def speak(self): print( "a %s goes %s!" % (self.name, self.sound()) ) def sound(self): pass
而後構造horseA對象的時候,傳遞name參數的值便可構造帶有name屬性的對象:
horseA = Horse("baima") horseA.speak()
__init__()
是在調用Horse()的時候自動被調用的,因爲Horse類中沒有定義構造方法,因此將搜索繼承自父類的構造方法__init__()
,發現定義了,因而調用父類的構造方法,並將對象名horseA傳遞給self參數,而後設置該對象的name屬性爲"baima"。
python設置或添加對象的屬性和其它語言很是不一樣,python能夠在任意地方設置對象的屬性,而沒必要先在構造方法中聲明好具備哪些屬性。好比前面在speak()方法中經過self.name = name
設置,此外還能夠在main.py文件中添加對象的屬性。例如添加一個color屬性:
horseA = Horse("baima") horseA.color = "white"
只要經過self.xxx
或者obj_name.xxx
的方式設置屬性,不管在何處設置都無所謂,都會是該對象獨有的屬性,都會被表明名稱空間的__dict__
屬性收集到。
horseA.__dict__