<style>
.red {
color: red;
}
.blue{
color: blue;
}
.green{
color: green;
}
</style>
# 函式 (Function)
## 5-1 認識函式
- 函式(Function)是<span class='red'>將一段具有某種功能或重複使用的statement寫成獨立的程式</span>。
- 然後給予名稱以供後續呼叫使用,這樣可以簡化程式提高可讀性
- 有時我們也稱函式(Function)為**方法(method)**, **程序(procedure)** 或**副程式(subroutine)**
:::success
使用函式(Function):
- 優點: <span class='red'>重複使用性</span>使程式會變得比較精簡、<span class='red'>可讀性提高</span>(容易理解有利於偵錯)
- 缺點:因為多了一道呼叫的程式,執行的速度自然比直接將statement寫進程式裡面慢一點
:::
## 5-2 定義函式
```python=
def FunctionName([Parameters]):
statements
[return|ReturnValues]
```
:::success
1. 若不需要return值回去,則可以省略return statement
2. 省略return statement。<span class='red'>嚴格上來說並不是沒有回傳值,而是回傳一個None的特殊值</span>
3. 若有多個return value則以逗號(,)相隔
:::
## 5-3 呼叫函式
- 要注意函式的參數(parameter),再帶入參數時順序不能用錯
- <span class='red'>當直譯器讀取到該行程式碼為函式時,會先將這幾行statement存放在記憶體,暫時不執行</span>
- 當直譯器讀取到呼叫的函式時,控制權會轉移到該函式,等該函式執行完後,才會將控制權再轉回呼叫函式的的地方
:::success
1. <span class='red'>當程式中有多個函式定義時,Python並沒有規定這些函式定義的前後順序,只要在呼叫某個函式時,其函式定義已經存放在記憶體即可。</span>
2. 我們將函式定義中的參數稱為「形式參數」(formal parameter) 或「參數」(parameter)。
3. 而函式呼叫中的參數稱為「實際參數」(actual parameter) 或「引數」(argument)。
:::
## 5-4 函式的參數
### 5-4-1 參數的傳遞方式
### 傳值呼叫(Call by Value)
- Python並不允許程式設計人員選擇參數傳遞的方式。
- 當參數屬於不可改變內容的物件(數值、字串、tuple)時,會採取**傳值呼叫(Call by Value)**。
- <span class='red'>此時函式無法改變參數的值,因為傳遞給函式的是參數的值,而不是參數的位址。</span>
```python=
def swap(a,b):
temp = a
a = b
b = temp
x, y = 1, 2
print(x, y)
swap(x, y)
print(x, y)
```
>Output:
>> 1 2
1 2
:::success
1. 由於變數x, y為數值,<span class='red'>屬於不可改變內容的物件</span>,因此上方程式碼第9行屬於傳值呼叫(Call by Value)。
2. 因為a, b參數和x, y變數是不同物件,被交換的是a, b參數,而x, y變數的值不受影響。
:::
### 傳址呼叫(Call by Reference)
- 相反的,當參數屬於可改變內容的物件(list, set, dict)時,會採取**傳址呼叫(Call by Reference)**。
- <span class='red'>此時函式就可以改變參數的值,因為傳遞給函式的是參數的位址,而不是參數的值。</span>
```python=
def swap(a):
temp = a[0]
a[0] = a[1]
a[1] = temp
x = [1, 2]
print(x[0], x[1])
swap(x)
print(x[0], x[1])
```
>Output:
>> 1 2
2 1
:::success
1. 由於變數x為list,<span class='red'>屬於可改變內容的物件</span>,因此上方程式碼第9行屬於傳址呼叫(Call by Reference)。
2. 因為a參數和x變數是相同物件(參照相同位址),當a參數值被交換了,x變數的值也會有影響。
:::
### 5-4-2 關鍵字引述
- Python預設採取<span class='red'>位置引述(Position Argument),函式呼叫裡面的引數順序必須對應函式定義裡面的參數順序</span>,一旦寫錯順序,會導致對應錯誤。
- 但有些參數順序實在不好記,此時可以使用<span class='red'>關鍵字引數(keyword argument)來做區分,也就是在呼叫函式時指定引數所對應的參數名稱</span>。
```python=
def trapezoidArea(top, bottom, height):
area = (top + bottom) * height / 2
print("梯形面積為:", area)
trapezoidArea(10, 20, 5)
trapezoidArea(10, height = 5, bottom = 20)
trapezoidArea(height = 5, bottom = 20, top = 10)
```
>Output:
>>梯形面積為: 75.0
梯形面積為: 75.0
梯形面積為: 75.0
### 5-4-3 預設引述值
- 我們可以在定義函式時設定預設引數值(default argument value),這樣當函式呼叫裡面沒有提供某個引數時,就會採取預設引數值。
- 這種擁有預設引數值的引數稱為**選擇性引數(optional argument)**,必須放在一般引數的後面。
```Python=
def teaTime(dessert, drink = "紅茶"):
print("我的甜點是", dessert, ",甜點是", drink)
teaTime("馬卡龍", "咖啡")
teaTime("帕尼尼")
teaTime(drink = "奶茶", dessert = "三明治")
teaTime("紅豆餅", drink = "綠茶")
```
>Output:
>> 我的甜點是 馬卡龍 ,甜點是 咖啡
我的甜點是 帕尼尼 ,甜點是 紅茶
我的甜點是 三明治 ,甜點是 奶茶
我的甜點是 紅豆餅 ,甜點是 綠茶
### 5-4-4 任意引述串列
- Python 支援**任意引數串列(arbitrary argument list)** 的功能,也就是<span class='red'>函式可以接受不限定個數的參數</span>。
- 只要定義函式時將參數加上星號( * ),表示接受不限定個數的參數。
```python=
def add(*numbers):
total = 0
print("numbers = ", numbers, type(numbers))
for i in numbers:
total = total + i
return total
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 2, 3, 4))
print(add(1, 2, 3, 4, 5))
```
>Output:
>> numbers = (1,) <class 'tuple'>
1
numbers = (1, 2) <class 'tuple'>
3
numbers = (1, 2, 3) <class 'tuple'>
6
numbers = (1, 2, 3, 4) <class 'tuple'>
10
numbers = (1, 2, 3, 4, 5) <class 'tuple'>
15
## 5-5 函式的回傳值
- 原則上,<span class='red'>在def區塊內的敘述執行完畢之前,程式的控制權都不會離開這個函式</span>。
- 不過有時我們可能需要提早離開函式,返回呼叫函式的地方,此時可以使用 return 敘述。
- 或者我們可能需要從函式回傳某個值或某些值,此時可以使用 return 敘述,後面再加上傳回值。
## 5-6 全域變數與區域變數
- 變數的有效範圍(scope),指的是程式的哪些敘述能夠存取變數的值。
- 大部分 Python 的變數都只有一種有效範圍,也就是程式的所有敘述都能存取變數的值,這種稱為全域變數(global variable)。
- 但在函式內定義的變數則稱為區域變數(local variable),只有函式內的敘述能夠存取區域變數的值。
```python=
def func1():
x = 1
print("x = ", x)
func1()
print("x = ", x)
```
>Output:
>> x = 1
ERROR!
Traceback (most recent call last):
File "< main.py >", line 7, in < module >
NameError: name 'x' is not defined
## 5-7 遞迴函式
- <span class='red'>遞迴函式(recursie function)是可以呼叫自己本身的函式。</span>
- 若函式 f1( ) 呼叫函式 f2( ),而函式 f2( ) 又在某種情況下呼叫函式f1( ),那麼函式 f1( ) 也可以算是一個遞迴函式。
- <span class='red'>遞迴函式通常可以被 for 或 while 迴圈取代</span>。
- 由於遞迴函式的邏輯性、可讀性及彈性均比迴圈來得好,所以在很多時候,尤其是要撰寫遞迴演算法,還是會選擇遞迴函式。
```python=
# 使用迴圈
def factorial(n):
result = 1
for i in range(1, n+1):
result = result * i
print(n,"! = ", result)
factorial(eval(input("Please input a number: ")))
```
```python=
# 使用遞迴
def F(n):
if n == 0:
return 1
elif n > 0:
# n! = n * (n-1)!
return n * F(n-1)
elif n < 0:
return -1
result = F(eval(input("Please input a number: ")))
print("Result = ", result)
```
:::success
1. 很明顯的遞迴函式比 for 迴圏來得有彈性。
2. 遞迴函式的語法並不難,重點在於如何設計遞迴演算法,而這需要演算法的基礎。
:::
## 5-8 lambda運算式
- Python提供一個<span class='red'>lambda關鍵字,可以用來建立小的匿名函式</span>。
- 所謂的匿名函式(anonymous function),指的是沒有名稱的函式。
```python!
lambda arg1, arg2, ...: expression
```
- lambada運算式會產生一個函式物件,arg1, arg2, ...就相當於函式定義的參數。
- expression 就相當於函式定義的主體 我們可以在 expression 中使用
arg1, arg2, ...這些參數。
```python=
add = lambda a,b: a+b
print(add(1, 2))
print(add(400, -20))
```
>Output:
>>3
380