註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!
前面兩篇我們從什麼是類別和物件的基本介紹起,
講到如何初始化及設定屬性、方法,以及如何運用繼承。
這篇我們首先要談的是,
什麼時候該用類別/物件,什麼時候又該用模組呢?
其實答案很簡單:
看怎麼用比較方便,就怎麼用XD!
讀者可能會覺得這是廢話,先別著急,讓筆者解釋一下。
在程式的世界裡,當我們遇上問題時,
大部分的狀況下都不會只有一種解法。
不同的解法,會有不同的優劣性,
扣除掉一些根本效率的不同,剩下的就只是取捨問題。
所以當碰到多個可行的選項,在考慮用哪一個方法時,
請先問問自己以下的問題:
以上三個問題會影響實務上的選擇,所以你可以基於節省空間考量而選方法A,
或基於節省時間而選方法B,亦或是為了架構比較好選方法C,
不會有絕對的對錯,只是看考量而已。
扯遠了,回過頭來看物件/類別及模組的抉擇。
通常模組會比物件/類別還要精簡,因為不會牽扯到太多額外的東西;
但模組在一個程式中import時,只會產生同一個東西,
而類別可以產生多個不同的物件。
當你的程式都是一些工具型函式,屬於那種呼叫完就會經過一番處理再輸出,
但不會需要產生多個不同的物件來處理事情的話,
以模組的型式處理會相當簡單。
舉例來說,一個學校只會有一個校長,校長可能有一些事情是他的權責範圍,
假設我們只鎖定一間學校,那校長能做的事情應該就可以直接以一個模組來囊括,
因為同一間學校總不會出現第二位校長嘛!所以我們可以用模組來定義所有校長的業務。
但學生就不行了,我們會有阿明,小美,HowHow跟其他路人甲乙丙丁,
所以學生就適合建立一個Student的類別來生成多個物件。
另一點是阿明和小美雖然都是學生,但它們會有屬性的差異,
這種有相同性質但屬性質不同的,最適合使用類別與物件來處理了!
另一方面,模組是不行繼承的,如果你有繼承的需求的話,
類別與物件就是唯一的選擇。
最後一點,如果可以用簡單的方式如字典、串列甚至一般函式來處理的話,
不一定非得要模組或類別/物件不可。
接著我們來談談類別方法及類別屬性。
類別方法是指會影響整個類別的方法,類別屬性則是指整個類別所共有的屬性。
舉例來說,如果我們想要知道今天我們總共加入了幾個Student類別的物件,
我們可以這麼寫:
class Student():
cnt = 0 # 這個變數是屬於整個Student類別的
def __init__(self, name, score):
Student.cnt += 1 # 每次新開一個Student的物件,計數器就會+1
self.name = name
self.score = score
def readMyName(self):
print('聽清楚了,我的名字是' + self.name + '!!!')
def compare(self, b):
diff = sum(self.score.values()) - sum(b.score.values())
# 有冒號的式子如果底下程式碼只有一行,也可以選擇直接和判斷式寫成同一行
if diff > 0: print(self.name + '贏了!')
elif diff == 0: print('什麼?竟然平手?!')
else: print('可...可惡,難道,這就是' + b.name + '真正的實力嗎?')
@classmethod
def getCount(cls):
print('目前的學生總數:%d' % cls.cnt) # 別忘了print也可以用format類型的形式
print('開始之前')
Student.getCount()
ming = Student('阿明', {'數學':55, '英文':70, '物理':55})
ming.readMyName()
Student.getCount()
mei = Student('小美', {'數學':90, '英文':88, '物理':100})
mei.readMyName()
Student.getCount()
howhow = Student('HowHow', {'數學':80, '英文':60, '物理':40})
howhow.readMyName()
Student.getCount()
結果應該像這樣:
C:\Users\Desolve>python fromzero.py
開始之前
目前的學生總數:0
聽清楚了,我的名字是阿明!!!
目前的學生總數:1
聽清楚了,我的名字是小美!!!
目前的學生總數:2
聽清楚了,我的名字是HowHow!!!
目前的學生總數:3
對於類別方法,我們會在前面加上"@classmethod",
同時由於"class"已經是Python的保留字(用來定義類別),
所以在使用時Python是給定"cls",用來指稱這個類別。
不過直接使用Student.cnt也可以,意思是一樣的。
所以請留意類別方法/屬性和物件方法/屬性的差異,
前者是屬於整個類別,後者是會依照產生的物件來操作。
可以看到即便在開始之前,
我們還沒加入任何Student,類別屬性就已經存在了。
還有一種比較特別的方法叫靜態方法。
使用"@staticmethod"來開頭,不需要self或cls參數,
通常用以處理可以固定不變,不受其他屬性影響的東西。
例如:
class Desolve():
@staticmethod
def ads():
print('無情工商時間!')
print('從LeetCode學演算法是一系列非常好的課程!')
print('https://bit.ly/leetcodeall')
Desolve.ads()
同樣,無須Desolve類別產生物件,這個方法就能被使用(甚至也沒有用到其他屬性)。
一個類別也可以繼承不只一個類別,例如假設我們有A, B兩個類別,
想用一個C類別同時繼承它們:
class C(A, B):
...
這麼一來,C就會同時擁有A跟B的屬性和方法。
那麼,如果相衝突(比方說重名)怎麼辦?
class A():
def __init__(self):
self.name = 'A'
print('A')
print('Name = ' + self.name)
class B():
def __init__(self):
self.name = 'B'
print('B')
print('Name = ' + self.name)
class C(A, B):
pass
test = C()
輸出應該會如下:
C:\Users\Desolve>python fromzero.py
A
Name = A
簡單來說,當A跟B有相同的方法時,
呼叫方法會呼叫哪一個,端看你把誰寫前面,寫越前面的優先程度越大。
(讀者可以自行將C(A, B)改成C(B, A)看看結果)
其他還有一些更深入的變化,我們就不在這個系列討論了,
有興趣的讀者可以再深入研究繼承多個類別的變化。
最後,我們來談談特殊的方法:
當我們在Python內使用那些 ==, !=, >=, <=, >, <......等比較運算子時,
其實是依靠Python有對這些東西做定義,才知道如何去做比較;
但是對於我們自己生成的類別來說,想要比較兩個相同類別的物件,
可能就需要我們自己定義了。
例如說假設有一個類別叫nmod3(),會紀錄數字並判斷餘數是否相等:
class nmod3():
def __init__(self, num):
self.num = num
def __eq__(self, n2): # 定義 "=="這個運算子為是否除以3的餘數相同
return self.num % 3 == n2.num % 3
a = nmod3(11) # 除以3餘2
b = nmod3(18) # 除以3餘0
c = nmod3(17) # 除以3餘2
print(a == b)
print(a == c)
print(b == c)
可以被特別定義的除了比較運算子以外,還有之前我們提到過的算術運算子等。
這裡稍微列出一些提供參考:
# 這些都是兩個物件相比,我們假定後面的物件叫b。
__eq__ => self == b
__ne__ => self != b
__lt__ => self < b
__gt__ => self > b
__add__ => self + b
__mul__ => self * b # 原來字串的乘法是這麼弄出來的XD
__len__ => len(self) # 可以定義什麼是你的物件的"長度"
...(其他有需要再Google即可XD)
那我們一樣來練習一下題目吧!
請參照之前的Student類別,
假設今天有一個科系要求是(數學 * 2 + 英文 * 5)的加權分數採計,
定義__gt__(也就是>), __eq__(也就是==),
用上面的採計方式及重新定義的比較運算子來寫成新的A.compareE(B)函式,
假設:
A的加權分高於B -> A的名字 + ' > ' + B的名字
A的加權分等於B -> A的名字 + ' == ' + B的名字
A的加權分小於B -> A的名字 + ' < ' + B的名字
並分別讓阿明和HowHow比較、阿明和小美比較、小美和HowHow比較,輸出結果。
(請保留之前compare的函式及呼叫的比較,我們兩種都要比呦!)
承上,請使用類別方法和屬性,在每次進行比較時就將總比較次數+1,
並print出來,這邊的「比較」是指compare()及compareE()都算。
辛苦啦!那我們就明天見囉!