上回我們完成了分析問題、實質編程和處理異常的環節,總算有一個可以面世使用的程式。
要完成一個簡單的編程方案,最後的一步往往是簡化架構,亦即是 code refractoring。
這一步比較進階,因為每個人對「簡化」的定義不同,在不同的企劃裡也有不同的需求。如果您是新手的話,不用著急,因為隨著您的編程經驗增加,會更加熟能生巧。
這裡也沒有一個既定的答案,因為不同的簡化各有利弊。譬如您可能減少了一些不必要的功能,但不覺地減低了可讀性(readability)。所以,簡化架構也是需要取捨平衡的一門學問。
我們的底線(bottomline)是令程式編程變得更容易管理和更新。
這裡我們提供 3 個思考的方向:
- 使用功能(function)包裝代碼
- 減低可變性(mutability)
- 使用物件導向概念(object oriented)
使用功能(function)包裝代碼
第一個可以讓我們改良架構的方法是使用自訂功能(custom function)包裝代碼。
這是因為如果我們的代碼是一個 function 的形式,那麼我們在其他地方使用這個程式時,只需要輸入 customFunction() 便可以運行代碼。
另一方面,我們亦應該把重複使用的代碼濃縮成自訂功能,讓我們更改代碼時,不用東找西找需要更改的地方。

# 匯入 library
import random
from sys import exit
# 定義常數(constant)及函數(variable)
min_x = 1
max_x = 100
answer = random.randint(min_x,max_x)
def loop():
global min_x, max_x
# 定義循環(loop)
while True:
# 先檢查 guess 是否正整數,如果不是,要求用家重新輸入正整數,直到 guess 是一個正整數
guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
if guess.isnumeric():
guess = int(guess)
break
# 比較用家提供的數字及範圍與答案
if guess == answer:
exit('恭喜您猜對了!答案是 ' + str(answer))
# 限制 min_x,max_x 和 guess 的關係
elif (guess < answer) and (min_x < guess):
min_x = guess
elif (guess > answer) and (max_x > guess):
max_x = guess
def main():
while True:
loop()
if __name__ == '__main__':
main()
我們在這個版本裡,把每一個循環(iteration)所需的代碼定義在 loop() 這個自訂功能裡。而我們透過 main() 這一個自訂功能處理循環(loop)。
這樣,當我們在其他地方呼叫 loop() 的時候,我們便可以直接進入到一個循環裡。這對於編程排錯(debugging)有幫助。
我們在另一篇教學有討論猜數字遊戲裡使用 global 的用意,有興趣可以參考這裡:局部 local 與總體 global 函數有何不同?教您使用 global keyword
減低可變性(mutability)
這個比較有爭議,在於這樣會把編程弄得稍微複雜,但也不乏潛在的好處。
可變性(mutability)指的是我們能否在已定義函數後更改其值,而不用建立新的物件(object)。在 Python 裡,可變(mutable)的函數像是列表(list),而不可變(immutable)的函數像是元組(tuple)。
減低可變性即是減低使用可變(mutable)函數。其中一個體現就是避免使用 global 關鍵字。潛在的好處是:
- 避免在不必要的自訂功能裡錯誤更改 global 函數。許多時我們的 global 函數其實只在某個功能裡使用,但如果我們習慣使用 global 函數,可能會意外地改變了函數值
- 比較容易把邏輯轉換到其他只使用不可變(immutable)函數的編程語言

# 匯入 library
import random
from sys import exit
def game(min_x, max_x, answer):
while True:
# 先檢查 guess 是否正整數,如果不是,要求用家重新輸入正整數,直到 guess 是一個正整數
guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
if guess.isnumeric():
guess = int(guess)
break
# 比較用家提供的數字及範圍與答案
if guess == answer:
exit('恭喜您猜對了!答案是 ' + str(answer))
# 限制 min_x,max_x 和 guess 的關係
elif (guess < answer) and (min_x < guess):
return guess, max_x, answer
elif (guess > answer) and (max_x > guess):
return min_x, guess, answer
else:
return min_x, max_x, answer
def main():
game_0 = game(1, 100, random.randint(1, 100))
while True:
game_0 = game(*game_0)
if __name__ == '__main__':
main()
在以上的例子裡,我們把 loop() 改為 game() 這個功能。而重點是,loop() 沒有函數輸入(input argument),但 game() 卻有 3 個函數輸入。
game() 所要求的函數其實就是我們本來的 global 函數(例如 min_x 和 max_x)。而我們本來在 loop() 所更新的 min_x 和 max_x 變成了 game() 的回傳值(return)。
留意在 main() 這個功能裡,我們先定義一個「起始值」game_0。這個 game_0 呼叫第一個循環(iteration),而回傳一個元組,含有 min_x、max_x 和 answer 3 個函數。這個元組是不可變(immutable)的。
我們接著把 game_0 這個元組作為函數輸入(argument)再次放進 game() 這個功能,進行第二、三、四等的循環。每個循環結束後,我們再把循環的回傳放進 game() ,直到用戶猜對數字。
這裡我們避免了使用可變(mutable)的 global 函數處理 min_x 和 max_x 的更新。我們改為使用不可變的元組傳遞 min_x 和 max_x 的更新。
某程度上,這個方法其實類似於遞迴(recursion)。雖然記憶體的使用可能比較多,但是也不乏好處。
使用物件導向概念(object oriented)
最後一個方法就是使用物件導向的概念(object oriented principles)。這個是一個普遍備受認證的概念,我們一起試試把這個猜數字遊戲變成物件導向?

import random
class Game:
def __init__(self, min_x, max_x):
self.min_x = min_x
self.max_x = max_x
def play(self):
self.answer = random.randint(self.min_x, self.max_x)
while True:
while True:
# 先檢查 guess 是否正整數,如果不是,要求用家重新輸入正整數,直到 guess 是一個正整數
guess = input('請從 ' + str(self.min_x) + ' 到 ' + str(self.max_x) + ' 猜一個數字:')
if guess.isnumeric():
guess = int(guess)
break
# 比較用家提供的數字及範圍與答案
if guess == self.answer:
print('恭喜您猜對了!答案是 ' + str(self.answer))
break
# 限制 min_x,max_x 和 guess 的關係
elif (guess < self.answer) and (self.min_x < guess):
self.min_x = guess
elif (guess > self.answer) and (self.max_x > guess):
self.max_x = guess
def main():
game = Game(1,100)
game.play()
if __name__ == '__main__':
main()
以上我們定義了 Game 作為一個物件(object)。留意我們要求建立新的 Game 物件時,需要提供 2 個函數:min_x 和 max_x。我們把他們在物件裡儲存為 self.min_x 和 self.max_x。
這 2 個函數是一個物件的個體函數(instance variable),既可以操作物件時更改(代替 global 的功能),亦可以避免在其他地方不小心更改了他們的數值。
我們亦定義了 Game.play() 的功能,以類似於開首使用 global 函數的方法處理 min_x 和 max_x 的更新。
定義了 Game 這個物件也有很多好處,包括我們可以同時建立多個 Game 的物件,而他們之間不會影響對方。這令 Python 能夠同時處理(multithreading)多個用戶使用 Game 來玩猜數字遊戲。
我們也可以定義一個物件函數(class variable)儲存每個用戶使用 Game 時的結果,弄成一個龍虎榜。
教學完整代碼
最後送給大家這篇教學的 Google Colab 完整代碼。如果您不懂得使用免安裝又好用的 Google Colab Notebook,記得閱讀這篇教學了:新手 1/3:5 分鐘免安裝學習 Python?Google Colab Notebook 幫緊您!
總結
經過上一篇教學,我們完成了編程設計和異常處理。這一篇教學把我們的焦點放在改良編程裡,讓我們可以把程式編程變得更容易管理和更新。




















