iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
0
Software Development

從零開始學Python系列 第 14

[Day 14] 從零開始學Python - 物件與類別:我們不一樣,每個人都有不同的際遇(下)

  • 分享至 

  • xImage
  •  

註:本文同步刊載在Medium,若習慣Medium的話亦可去那邊看呦!

前面兩篇我們從什麼是類別和物件的基本介紹起,
講到如何初始化及設定屬性、方法,以及如何運用繼承。

這篇我們首先要談的是,
什麼時候該用類別/物件,什麼時候又該用模組呢?

其實答案很簡單:
看怎麼用比較方便,就怎麼用XD!

讀者可能會覺得這是廢話,先別著急,讓筆者解釋一下。
在程式的世界裡,當我們遇上問題時,
大部分的狀況下都不會只有一種解法
不同的解法,會有不同的優劣性,
扣除掉一些根本效率的不同,剩下的就只是取捨問題。
所以當碰到多個可行的選項,在考慮用哪一個方法時,
請先問問自己以下的問題:

  1. 題目(或現實的目標)是否有限制,導致某些方法其實不能用?
  2. 這當中,哪些是前面寫起來比較簡單,後面維護(或擴充)比較麻煩的?
    哪些是前面寫起來比較複雜,後面卻可以節省力氣的?
  3. 承2,在大前提的目標下,這個架構有需要考慮到後面的問題嗎?

以上三個問題會影響實務上的選擇,所以你可以基於節省空間考量而選方法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)

那我們一樣來練習一下題目吧!

  1. 請參照之前的Student類別,
    假設今天有一個科系要求是(數學 * 2 + 英文 * 5)的加權分數採計,
    定義__gt__(也就是>), __eq__(也就是==),
    用上面的採計方式及重新定義的比較運算子來寫成新的A.compareE(B)函式,
    假設:
    A的加權分高於B -> A的名字 + ' > ' + B的名字
    A的加權分等於B -> A的名字 + ' == ' + B的名字
    A的加權分小於B -> A的名字 + ' < ' + B的名字
    並分別讓阿明和HowHow比較、阿明和小美比較、小美和HowHow比較,輸出結果。
    (請保留之前compare的函式及呼叫的比較,我們兩種都要比呦!)

  2. 承上,請使用類別方法和屬性,在每次進行比較時就將總比較次數+1,
    並print出來,這邊的「比較」是指compare()及compareE()都算。

辛苦啦!那我們就明天見囉!


上一篇
[Day 13] 從零開始學Python - 物件與類別:我們不一樣,每個人都有不同的際遇(中)
下一篇
[Day 15] 從零開始學Python - 檔案讀寫:妳出現在我詩的每一頁(上)
系列文
從零開始學Python30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言