有效率的撰寫Python — 類別Class
什麼是類別?
類別(class)可以說是資料的設計藍圖。在這個藍圖裡面,我們會把資料的屬性(attribute)與資料的方法(method)都寫入這個藍圖裡面。
類別具有的功能被稱為是方法,其實這個方法就相當於是函式。實際上也是以函式的方式在撰寫。
一個類別裡面可以有很多的方法。每個方法都是一個函式,而每個函式裡面,又可以包含多個處理程序。換句話說,一個類別匯集了多個函式。
除了函式外,類別裡面還容納了很多的屬性。屬性則是由各種的變數組成。換句話說,一個類別也匯集了多個變數。因此,我們可以說類別是函式與變數的集合體。
使用類別有什麼好處?
讓程式碼便於閱讀
當程式的規模越來越大時,必須好好的規劃各個程式的分類方式,才有辦法在後續維護時可以快速的找到需要調整的地方。另外,如果專案是透過多人共同開發的話,如果沒有一個容易理解與閱讀的程式碼結構,大家將很難可以一起協作。
便於大量生產
如果碰到需要大量產出相類似的程式碼的時候。使用類別就是一個很大很大的加速方式。我們可以使用類別的繼承等性質,並且調整類別的屬性與方法,便可以快速而且大量的產出類似的程式碼。
如何撰寫簡單的類別?
前面說過,類別像是一個程式的藍圖。我們需要利用這個藍圖製作出實際的物件來使用,透過程式的藍圖產生實際的物件的程序就叫做「實體化」。
還記得所有的東西在Python中都是物件?Python程式就是用來操作這些物件。物件除了使用既有的之外,我們也可以自己定義物件。類別(Class)也是物件,我們也可以自己定義一個類別物件。定義類別的方式如下:
class 類別名稱: //程式區塊
上面的定義看起來不複雜吧,那到底程式區塊裡面是什麼?
沒錯,我們在前面提過,類別是「函式」與「變數」的集合體。因此類別的程式區塊裡面定義了函式與變數,讓類別成為函式與變數的集合體。在這裡面的函式我們又稱之為方法(method)。在類別命名的規則上,我們一般會採用第一個字為大寫字母來命名,而類別裡面的方法則會採用小寫字母。例如我們建立一個Person的類別,Person的第一個字母『P』為大寫:
class Person: pass
前面提到,類別是程式的設計藍圖,透過程式的藍圖產生實際的物件的程序就叫做「實體化」。下面的John與Amy都是用Person類別產生的物件,物件就是透過藍圖製造出的實體(instance)。這些被製造出的實體,在結構上會與藍圖一樣。
John = Person()
Amy = Person()
self參數
在類別當中宣告函式時,必須在函式的第一個參數位置必須填入self這個字。如過沒有採用這個規則,執行程式會發生錯誤。因為,在呼叫執行類別裡面的函式時,至少會傳遞一個參數(self),如果沒有設定,就會發生錯誤。這裡的參數self指的是「類別自己」,當然你也可以使用別的單字來取代self的使用,只是self已經是一般Python工程師約定成俗的慣用方法。
__init__方法
這是Python特殊的方法,如果在類別程式區塊中定義了__init__方法(init是initialize的縮寫),每次用到這個類別建立物件時,都會自動執行這個方法。我們會在這個__init__方法裡面建立初始化物件的變數。因此,我們可以事先將變數全部建立在__init__方法裡面。依照慣例,__init__方法裡面的第一個參數是self。
我們修改一下語法的定義:
class 類別名稱: def __init__(self,參數1,參數2,...): self.初始變數1 = 參數1 預先執行的處理程序 def 方法名稱1(self): 方法的處理程序
例如下面的Person類別,在__init__方法有一個last_name的變數。
class Person: def __init__(self): self.last_name ='Lewis'
接著用下面方式取用類別物件的變數值:
物件名稱.變數名 = 變數值
John物件的last_name結果是:
John.last_name
又如下面的Person類別:
class Person: bonus = 3000 def __init__(self, first_name, last_name): self.first_name =first_name self.last_name = last_name def salary(self): salary = 10000 + self.bonus return salary
如果將Person類別初始化為John物件:
John = Person('John','Lewis')
John的薪水可以這樣子取得:
John.salary()
上面的salary函式中,salary為10000加上bonus。而bonus變數被宣告在Person類別裡面,我們必須使用self.bonus才可以取得。
目前的bonus是個固定的數字,如果我們希望讓每一個人有不一樣的bonus,要如何改寫Person類別?
這時候,我們可以把bonus改成為類別的參數:
class Person: def __init__(self, first_name, last_name, bonus): self.first_name =first_name self.last_name = last_name self.bonus = bonus def salary(self): salary = 10000 + self.bonus return salary
透過參數的方式設定每個人的bonus值:
John = Person('John','Lewis',8000)John.salary()
得到的結果是:18000 。
類別可以繼承(Inheritance)
聽到「繼承」一詞我們會想到的例子是,小孩子生出來後漸漸的在外型長相或者是個性上會有某些方面跟父母一樣,我們會說這小孩子繼承了他父母的一些特徵。或者是小孩子接收了父母親一生中賺取的所有財產。
在Python程式中,繼承(Inheritance)是讓某個類別從另外一個類別承繼某些特性的功能。在這兩的類別間傳承的東西,正式類別中所宣告的資料與方法。繼承者被稱為子類別,而被繼承者則被稱為父類別。
繼承的語法
在子類別的地方,需要在括號中填入父類別的名稱。原本的父類別不需要進行調整。
class 類別名稱(父類別名稱): //程式區塊
下面試舉繼承的例子。
例1:
我們回到上面的Person類別。我們要以此類別作為父類別,示範一下如何繼承這個類別。
class Person: def __init__(self, first_name, last_name): self.first_name =first_name self.last_name = last_name
假設我們有一個情境需要產生一個物件,裡面一樣有 first_name、last_name的需要外,還需要一個title的屬性。這時候我們可以選擇「再寫一組類別」,比如說是主管(supervisor)類別:
class Supervisor: def __init__(self, first_name, last_name, title): self.first_name =first_name self.last_name = last_name self.title = title
那麼,如果過沒多久,我們有產生一個需求,就是要再增加一個角色,這時候如果像上面一樣一直增加類別,你是不是覺得這樣子寫程式好像有點累贅的感覺?
這樣子寫程式就不夠DRY了。
所謂的DRY就是要我們要讓程式「Don’t Repeat Yourself」。如果可以用一行可以解決的話,就不要重複寫第二行。因此,如果在這裡我們使用繼承的話,程式碼就會比較清爽,未來要維護也會比較輕省。
首先來看看下面程式碼,下面的Supervisor類別除了繼承了Person類別以外,不外加其他。
class Supervisor(Person): pass
如果我們透過 boss = Supervisor(‘John’,’Law’) 實體化一個物件。
boss = Supervisor('John','Law')
實體化物件boss繼承了Person類別的boss物件。這個boss物件是否具有跟Person類別一樣的屬性?
要得到這個答案,我們可以使用下面的方式呼叫看看first_name與last_name的屬性。
print(boss.first_name)print(boss.last_name)
由於我們在Supervisor類別中並沒有做其他的設定,只有讓Supervisor類別繼承Person類別。上面執行結果,仍然可以顯示出first_name與last_name的屬性。這可以說明了Supervisor類別「繼承」了Person類別中的first_name與last_name的屬性。
我們可以再舉一個例子看看。
例2:
假設有一個動物類別(Animal類別)。裡面有個cool變數與一個make_noice方法。
class Animal: cool = True def make_noice(self, noice): print(f"動物會發出 {noice} 的聲音")
建立一個Cat類別,跟上面一樣它單純繼承Animal類別,不加任何其他屬性。
class Cat(Animal): pass
我們可以實體化Cat類別ball,並且讓他執行make_noice方法:
ball = Cat()ball.make_noice("喵")
測試下面指令看看:
print(blue.cool)print(Animal.cool)print(Cat.cool)
由於我們在Cat類別中並沒有做其他的設定,只有讓Cat類別繼承Animal類別。
上面執行結果,三個指令的值都是True。其中,第一個True表示ball使用了Animal類別裡面的cool變數、第二個True表示Animal類別裡面的cool變數而第三個True則表示Cat類別使用了Animal類別裡面的cool變數。由此結果可以說明Cat類別「繼承」了Animal類別中的cool變數與make_noice方法。
此外,也可以使用isinstance(a,b)來判斷a是不是b的實體:
print(isinstance(blue,Cat))print(isinstance(blue,Animal))print(isinstance(blue,object))
結果一樣都是True。
表示ball是Cat類別的實體化,也是Animal類別的實體化,更是物件的實體化。因為在Python中,萬物皆為物件。
屬性繼承
再次使用Animal類別作為例子來說明:
class Animal: def __init__(self, name, species): self.name = name self.species = species def __repr__(self): return f"{self.name} 是一隻 {self.species}" def make_noice(self, noice): print(f"動物會發出 {noice} 的聲音")
這次我們先修改一下Animal類別,加入name與species兩個屬性,保留make_noice方法。
建立一個Cat類別,並加入name、species、breed、toy等四個屬性,其中name與species兩個屬性與Animal類別一樣。
class Cat(Animal): def __init__(self, name, species,breed, toy): self.name = name self.species = species self.breed = breed self.toy =toy
接著,指定ball為Cat類別。Cat類別繼承了Animal類別,但是Cat類別與Animal類別分別設定了name與species屬性。
ball = Cat("小球","貓","波斯", "老鼠")print(ball)
實體化ball之後,如果我們執行print()會顯示:
上面顯示的結果,實際上是執行Animal類別裡面的__repr__方法。可見得Cat類別繼承了Animal類別裡面的方法。
如果我們希望Cat類別使用Animal類別的name與species屬性,而不是單獨在Cat類別裡面自行設定name與species屬性的話,該如修改上面的程式碼?
請試著把Cat類別裡面的name與species屬性兩個屬性刪除,因為我們不想自行宣告:
self.name = nameself.species = species
接著換成下面的寫法,把Animal類別的name與species屬性引入:
Animal.__init__(self, name, species)
修改後的結果如下:
class Cat(Animal): def __init__(self, name, species, breed, toy): Animal.__init__(self, name, species) self.breed = breed self.toy =toy
測試一下結果
print(ball)print(ball.species)print(ball.breed)print(ball.toy)
每個屬性都正確顯示出來。
使用Super()
上面的Animal._init__在Python中有一個寫法,就是鼎鼎大名的super。我們可以試著換成super看看:
class Cat(Animal): def __init__(self, name, species, breed, toy): super().__init__(name, species) self.breed = breed self.toy =toy
測試一下,結果應該是一樣的。因此super是可以使用的。
由於我們設定Cat類別是為了給貓使用的。所以可以想像貓的種(species)應該是貓(Cat)。因此,我們可以再優化一下程式碼,讓預設species的屬性值為Cat。
class Cat(Animal): def __init__(self, name, breed, toy): super().__init__(name, species="貓") self.breed = breed self.toy =toy
執行後得到的結果會一樣。
我們也可以試著透過super來修改上面的Supervisor類別。
使用super來執行父類別first_name與last_name變數初始化,super下面再放入自己的title變數初始化。
class Supervisor(Person): def __init__(self, first_name, last_name, bonus, title): super().__init__(first_name, last_name, bonus) self.title = title
如果少了這個步驟,子類別所建立的物件將不會有first_name,last_name變數。
這樣子可以取得title的屬性。
print(boss.title)
除了類別的繼承外,也可以測試方法的繼承。我們回到上面的Person類別。原本Person類別裡面有一個salary方法。
class Person: def __init__(self, first_name, last_name, bonus): self.first_name =first_name self.last_name = last_name self.bonus = bonus def salary(self): salary = 10000 + self.bonus return salary
子類別Supervisor如下:
class Supervisor(Person): def __init__(self, first_name, last_name, bonus, title): super().__init__(first_name, last_name, bonus) self.title = title
接下來可以試試 salary方法。
print(boss.salary())
你的結果是不是也跟上面的一樣?