Python 教學系列文 (5) - 函數、其他常見資料結構
Python 基礎 (2)
函數(Function)
我們以前寫 print(‘Hello’) 時,其實就是在呼叫函數,這個函數會幫我們把我們傳入的 ‘Hello’ 印出來。其他像是 range()、type()、input() 等也都是函數,各有不同的用途。
我們也可以透過特定語法來定義自己的函數,透過函數可以幫我們達成「模組化」,省去重複的 code 同時提供更多彈性來執行類似的動作。
一個函數包含名稱、本體、輸入(Input)與輸出(Output),後兩者又叫做參數(Parameters)與回傳值(Return Values)。有時我們也會在函數最一開始的地方加入註解,來說明函數的使用方式以及參數 / 回傳值類型。
以下圖為例,輸入是蘋果,輸出是切半的蘋果,函數 h
的作用是把蘋果切半。
名稱
- 用關鍵字
def
來宣告函數,名稱接在def
後面 - 名稱通常會取與函數作用相關,便於使用者理解函數功能
- 使用函數時,用其名稱來呼叫函數
本體
- 把要執行的程式碼包在函數本體中
- 有時會在本體前面加上註解,用以說明函數功能
- 說明最好包含:輸入、輸出、作用
- 因為函數沒有限制變數的類別,所以最好在說明中講清楚
輸入(參數)
- 參數的作用是提供資料給函數操作
- 函數的參數可以自行命名(如下例的 n)
- 可以傳入多個參數,用逗號隔開
- 可以給定預設值(如下例的 n = 5)
輸出(回傳值)
- 回傳值的作用是把結果回傳給使用函數的人
- 使用
return
來控制函數的結束點,並將回傳值放在後面 - 若沒有
return
則會自動在最後加上return None
- 可放回傳多個結果,用逗號隔開
- 函數結束後會回到主程式,繼續執行後面的程式
以下是一個在 python 中的實際例子,輸入是一個數字 n
,輸出是一個清單 alist
,函數 get_1_to_n
的作用是獲取 1 ~ n 的清單。
1 | def get_1_to_n(n = 5): |
1 | Output: |
變數範圍(Scope of Variable)
變數依據生命週期的不同,分為全域變數與區域變數。
- 區域變數(Local Variable)
- 定義在函數內的變數稱為區域變數
- 只能在函數內使用,函數結束後變數也會跟著消失
- 全域變數(Global Variable)
- 定義在函數外的變數稱為全域變數
- 所有地方(包含函數內)都可以使用,直到程式結束執行才會消失
- 若函數內宣告與全域變數同名的變數,則會被當作是區域變數,對其進行的操作不影響全域變數
- 通常若我們需要拿到函數內的某個變數,我們會直接使用
return var
1
2
3
4
5
6
7
8
9
10
11
12
13scale = 3 # Global
def multiply(num):
return num * scale
def multiply_5(num):
scale = 5 # Local
return num * scale
print(scale)
print(multiply(2))
print(multiply_5(2))
print(scale)1
2
3
4
5Output:
3
6
10
3補充:在函數內修改全域變數與上一層變數的方法:Global & Nonlocal
可變物件(Mutable Object)與不可變物件(Immutable Object)
在 Python 中,不同資料類別又可以其性質分為可變物件與不可變物件。
分類 | 可變物件 | 不可變物件 |
---|---|---|
說明 | 被創造出來後,其值可以被改變的物件。 | 被創造出來後,其值無法被改變的物件。 |
舉例 | list, dict, set* | int, float, string, tuple |
修改 | 可以,依資料類別不同有不同修改方式,修改時記憶體位置不會改變。 | 無法,只能透過重新指派的方式,此時記憶體位置亦會被重新分配。 |
1 | # Mutable Object |
接著我們來看看記憶體位址的變化:
1 | # Let's take a look on the addresses of these objects |
1 | Output: |
參考 什麼是 Immutable & Mutable objects
*關於 set 可不可變其實有點爭議,在這裡先當作他是可變的
Pass by Assignment - Example Illustration
此處我們「不會」深入講當傳參數時發生了什麼事情,因為牽扯到一些記憶體跟參照等等的概念,我們會用幾個例子來說明何謂 Python 的 Pass by Assignment。
Python 中函數依據傳入參數的類別不同,會有不同的行為。
- 當傳入參數可變物件時:
- 若未經重新指派,而是在函數裡直接修改參數,則會原始變數的值也會一同被修改
- 若經重新指派,則視為全新的變數,原始變數不會被影響
- 當傳入參數為不可變物件時:
- 任何對參數的操作都不影響原始變數(除非使用全域變數方式修改)
聽起來很複雜對吧?我們直接用例子來看會比較清楚一些:
1 | def listchange(l): |
1 | Output: |
1 | def strchange(s): |
1 | Output: |
在以上的例子中,alist
為可變物件,因此做為參數傳入並在函數中修改時,原始的 alist
也一同被修改;而 astring
為不可變物件,因此做為參數傳入時,我們並無法直接修改他的值,只能透過重新指派的方式給予 'STRING'
這個值,而原始的 astring
依然存放 string
這個值沒有改變。
我們在撰寫函數時,比較好的方式是不要直接修改原始參數的值,而是將修改後的值存放在新的變數中,並作為回傳值傳回給呼叫函數的地方,以避免混淆的狀況。
遞迴(Recursion) - An example on factorial
遞迴是一種概念,指的是「在函數在執行過程中呼叫自己」的方法。這種技術對於解決某些複雜問題特別有用,例如處理樹狀結構、遞迴搜尋、組合數學等。以下是遞迴的基本概念和特性:
- 基礎案例(Base Case):遞迴函數必須有一個基礎案例,也就是遞迴呼叫的終止條件。當滿足這個條件時,遞迴將不再進行,從而避免無限迴圈。
- 遞迴案例(Recursive Case):如果沒有滿足基礎案例的條件,函數就會進入遞迴案例。在這個案例中,函數會呼叫一個較小的子問題版本。
- 問題簡化:遞迴案例通常將原始問題簡化為一個較小的子問題,直到滿足基礎案例為止。
1
2
3
4
5
6
7def find_fact(n):
if n == 1: # base case
return 1
else: # recursive case
recurse = find_fact(n-1)
result = n * recurse
return result
補充:遞迴深度的上限
關於函數還有很多可以講:Recursion 的設計方法、Call by Reference、Call by Value…。但有些東西太進階了,我們先停在這裡,以後有機會或是遇到的時候再細講。
其他常見資料結構
元組(Tuple)
- 與 list 類似,但是屬不可變物件
- 不同於 list 使用
[]
,tuple 使用()
- 一個元素的 tuple 須以
(item, )
表示 - 因屬不可變物件,故僅能以重新指派的方式修改其值,如下例:
1
2
3
4
5
6
7>>> mytuple = (11, 22, 33)
>>> saved = mytuple
>>> mytuple += (44,)
>>> mytuple
(11, 22, 33, 44)
>>> saved
(11, 22, 33) - zip()
1
2
3
4
5num = [1,2,3]
char = ['a','b','c']
CHAR = ['A','B','C']
for i in zip(num, char, CHAR):
print(i)1
2
3
4Output:
(1, 'a', 'A')
(2, 'b', 'B')
(3, 'c', 'C')
字典(Dictionary)
當我們需要了解某個字的讀音時,我們會去查找字典,在其中尋找對應的讀音。這種 {字: 讀音} 的配對,在 Python 中可以透過字典來實現。
- 字典的組成包含鍵(Keys,不可變)與值(Values,可變)
- 使用 Key 來尋找對應的 Value,以上述例子來說即為使用字尋找讀音
- Key 跟 Value 可以是任何資料類別,也可以不用一樣
- 字典是無序的(在
collections
這個 library 中有提供有序字典) - 若查找不存在的 key 則會報錯,可以使用
in
或dict.get()
來檢查
1 | mydict = dict() # or mydict = {} |
1 | Output: |
1 | # Continued from last cell |
1 | Output: |
集合(Set)
- 與數學中的集合概念類似,只能儲存唯一(Unique)元素,相同元素不會重複出現
- 因為是無序,故我們不能使用
set[0]
的方式來取值 - 可以使用
set.add(item)
與set.remove(item)
來新增與刪除元素 - 可以使用
set(list)
將 List 轉為 Set,藉此檢查清單中唯一元素個數 - 相關操作有聯集、交集、差集等等
1 | aset = {11, 22, 33} |
1 | Output: |
補充:特別注意 運算元優先順序!
統整
類別 | Tuple | List | Dict | Set |
---|---|---|---|---|
符號 | ( ) | [ ] | { } | { } |
可變性 | 不可變 | 可變 | 可變 | 可變 |
順序性 | 有序 | 有序 | 無序 | 無序 |