您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關python中代碼復用的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
最初代碼
3種動物牛Cow、羊Sheep、馬Horse發出的聲音各不相同,于是在同一個目錄下建立三個模塊文件:
$ tree . . |-- cow.py |-- horse.py `-- sheep.py
三個模塊文件的內容都只定義了各自的speak()函數:
# 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 叫聲!。于是為了讓代碼更具共性,或者說復用性更高,可以將各模塊文件中的動物名和叫聲都變得通用化。
目前來說,有兩種最基本的方式可以讓一段代碼變得更共性、共通用化:使用參數或變量、使用額外的輔助函數。當然,除此之外還有更多的方法,但目前來說這兩種是最基本的,也是最容易理解的。
使用參數(變量)讓代碼更具共性
首先讓動物名變得共性化。可以讓speak()中的動物名使用一個參數來替代。例如名為self的參數變量(之所以使用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))
它們現在在動物名上和參數名上已經完全相同,需要調用它們時,只需在函數調用處為他們傳遞不同的動物名即可。例如,在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()進行調用:
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()。
雖然上面的代碼變得"人不人鬼不鬼"(因為沒有使用面向對象的語法),但面向對象的基本目標達到了:共性的代碼全部抽取出去,實現最大程度的代碼復用。
self是什么
在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!
這正是期待的結果。
構造方法__init__()
上面是在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__
感謝各位的閱讀!關于“python中代碼復用的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。