# 程式碼模組化與應用
## 讓程式碼更容易被維護、除錯的方法
模組化可以想成許多的積木,可以快速被取用、組合成各種物品。
隨著程式碼越來越龐大複雜,設計程式時也應該遵循這樣的設計原則,包括:
- 執行程序模組化:使用`函式(Function)`
- 資料格式模組化:使用`物件(Object)`與`類別(Class)`
- 系統檔案模組化:使用`函式庫(Library)`、`套件(Package)`、`模組(Module)`與`應用程式介面(API)`
以上設計原則,都是為了程式可以更容易被維護、除錯。
## 執行程序模組化:函式(Function)
在Python中,我們使用`函式(Function)`將重複出現的程式碼,通常是一系列的程序,做成函式,可以隨時呼叫使用,增加開發效率、以及讓程式碼更容易維護。
主要好處如下:
* 減少撰寫重覆的程式碼
* 將程式碼以有意義的方式組織起來
* 相同的流程下,可藉由參數調整程式的行為
* 藉由函式庫可組織和分享程式碼
* 做為資料結構 (data structures) 和物件 (objects) 的基礎
### 函式的宣告
函式包含以下部分:
* 函式的名稱 (identifier)
* 函式的參數 (parameters),相當於輸入
* 函式的回傳值 (return value),相當於輸出
* 函式的本體 (body)
#### 函式的類型
函式分成兩種類型,有`回傳值`與`無回傳值`。
請參考以下範例程式碼:
```python=
# 無回傳值
def function1():
print("Hello")
# 有回傳值
def function2():
a = 0
a += 1
return a
#呼叫function 1
function1()
#呼叫function 2
print(function2())
```
#### 函式的參數
函式可以帶入`參數`,在函式內使用該參數進行運算,但也可以不帶入參數。
```python=
# 無帶入參數
def function1():
a = 0
b = 0
a += 1
b = a
return b
# 有帶入參數
def function2(a):
b = 0
a += 1
b = a
return b
# 呼叫function 1
print(function1())
# 呼叫function 2
x = 2
print(function2(x))
```
### <a id="var_scope"></a>變數範圍(Variable Scope)
在函式裡宣告使用的變數,與函式外的變數,有不同的使用範圍限制,我們稱為`Variable Scope`。
分成兩種:
- `廣域範圍(global scope)`
或稱module scope,範圍是單個檔案(*.py)。
模組(或app)是層層堆疊起來的,並不會說哪裡才是真正的global環境,要說的話,最接近的可能就是built-in的變數如True、None所宣告的地方吧
- `區域範圍(local scope)`
以函式function為範圍,scope伴隨函式被呼叫時建立,變數重新綁定
可以透過函式`globals()`、`locals()`輸出該scope的所有變數
### <a id="recursion"></a>函式進階使用技巧:遞迴
`遞迴(Recursion)`,定義如下。
```
一個函式,由自身定義或呼叫,且具備以下兩個條件,就叫做遞迴。
1. 可以反覆執行的過程
2. 跳出執行過程的出口
```
根據呼叫對象不同,遞迴可分成兩種
1. 直接遞迴:Function自身呼叫自己。
```python=
def Func(...):
...
if(...)
Func(...)
...
```
2. 間接遞迴:Function之間互相呼叫。
```python=
def Func1():
...
if(...)
Func2()
...
def Func2():
...
if(...)
Func1()
...
```
### 隨堂練習
1. 階乘的計算
階乘的基本定義如下
- n == 0, n! = 1
- n > 0, n! = n x (n-1)!, n正整數
請利用遞迴的技巧設計程式,計算 **4!** 為何?
參考程式碼如下:
```python=
def factorial(n):
if n == 0:
return 1
else:
return( n * factorial(n-1))
print(factorial(4))
```
2. 請設計程式,請使用者輸入身高(cm)與體重(kg),並且設計兩個版本的函式來計算BMI值,名稱叫做`bmi_calculator`
- 版本1. 有參數(身高與體重)、有回傳值(bmi值)
- 版本2. 無參數、無回傳值
`Note: BMI計算公式:BMI=(體重/身高^2)`
**參考程式碼:**
版本1. 有參數(身高與體重)、有回傳值(bmi值)
```python=
def bmi_calculator(height, weight):
bmi = weight/(height/100)**2
return bmi
user_height = int(input("請輸入身高(cm)> "))
user_weight = int(input("請輸入體重(kg)> "))
bmi_result = bmi_calculator(user_height, user_weight)
print("你的BMI是 {0:.2f}".format(bmi_result))
```
版本2. 無參數、無回傳值
```python=
def bmi_calculator():
user_height = int(input("請輸入身高(cm)> "))
user_weight = int(input("請輸入體重(kg)> "))
print("你的BMI是 {0:.2f}".format(bmi_result))
bmi_calculator()
```
## 資料格式模組化:物件與類別
由於物件與類別的應用,較適合大型程式應用,例如遊戲、大型網站,在此不特別介紹,若有興趣,請參考補充資料:[物件與類別](https://hackmd.io/@howkii-studio/object_class)
## 系統檔案模組化:函式庫、套件、模組、API
![](https://i.imgur.com/58qiZGg.png)
1. `模組(Module)`:一個.py檔案就是一個 module,裡頭可以定義 `variable`, `function`, `class`
2. `套件(Package)`:一個檔案資料夾,裡面存放多個module (.py檔案)
3. `函式庫(Library)`:根據特定用途,將多個Package集合在一個資料夾中
4. `應用程式介面(API, Application Programming Interface)`:Library開發者可定義哪些module中的function可以給別人引用,這些function就稱為API。對於API引用者,不需要了解實作內容,只需要說明文件知道API用途,自行使用開發新程式。
### 函式庫引用範例:使用第三方函式庫openpyxl
我們再回頭看這個使用第三方函式庫`openpyxl`的範例
```python=
import openpyxl
import os
# os.chdir 是 python 切換到電腦指定路徑的方法
os.chdir(r"/Users/chaoyen/Downloads") # 請填寫自己電腦裡Excel檔案的絕對路徑
wb = openpyxl.load_workbook('produceSales.xlsx') # 請填寫要處理的Excel檔案名稱
sheet = wb.worksheets[0]
# 要更正的品名與其單價
price_updates_dict = {'Garlic': 1.99}
#使用for loop掃描所有A欄品名,如果比對一致,就進行更正與上色
print("Processing...")
for rowNum in range(2, sheet.max_row, 1):
produceName = sheet.cell(rowNum, 1).value
if produceName in price_updates_dict:
sheet.cell(rowNum, 2).value = price_updates_dict[produceName]
# openpyxl函式庫中的styles模組的Font API
sheet.cell(rowNum, 2).font = openpyxl.styles.Font(color='FF0000')
# 將結果另存新檔
wb.save('produceSales_update.xlsx')
print("Done!")
```
小提示:如果第19行想直接引用`Font`,可以在第三行新增這行程式碼
```python=
from openpyxl.styles import Font
```
### 硬體應用API實務:Moxa UC-8100 API Library
以下內容節錄自Moxa UC-8100的[User Manual](https://www.moxa.com/en/products/industrial-computing/arm-based-computers/uc-8100-series#resources),跟購買者說明,如何引用Moxa定義的API,來開發程式,控制UC-8100這台IoT Gateway
![](https://i.imgur.com/fivj1TC.png)
有經驗的開發者看到這份手冊,就可以知道Moxa的API主要是以C語言撰寫的,在呼叫API前,要安裝相關套件。
### 軟體應用API實務: Google Map API Library
以下內容節錄自[Google Maps Platform](https://cloud.google.com/maps-platform/?hl=zh&utm_source=google&utm_medium=cpc&utm_campaign=FY18-Q2-global-demandgen-paidsearchonnetworkhouseads-cs-maps_contactsal_saf&utm_content=text-ad-none-none-DEV_c-CRE_467208339263-ADGP_Hybrid%20%7C%20AW%20SEM%20%7C%20BKWS%20~%20Brand%20%7C%20EXA%20%7C%20Google%20Maps%20API-KWID_43700057416638020-aud-596763661393%3Akwd-401469081526-userloc_9073389&utm_term=KW_google%20map%20api-ST_Google%20Map%20API&gclid=Cj0KCQiAhs79BRD0ARIsAC6XpaWYRBX80JE9KdV-TgJWHryK-JRvcFf-5jeQwNm7foY8VWd8W--J2iQaArC3EALw_wcB),這裡提供給想要使用Google Map API來做應用的開發者
![](https://i.imgur.com/raeZ2cD.png)
有經驗的開發者看到這個網站的說明,就可以知道Google Map的API主要是以Javascript撰寫的。
### 隨堂練習
請設計大樂透的報明牌程式,從1~49裡面,隨機抽出六個不重複的數字
請引用`random.sample`這個模組來隨機取出六個數字
**不直接引用sample API**
```python=
import random
num = 49
num_list = []
for i in range(0, num, 1):
num_list.append(i+1)
# random.sample
print(f'Your lucky number: {random.sample(num_list, 6)}')
```
**直接引用sample API**
```python=
from random import sample
num = 49
num_list = []
for i in range(0, num, 1):
num_list.append(i+1)
print(f'Your lucky number: {sample(num_list, 6)}')
```
###### tags: `Python程式設計入門`