<style>
.flex-row {
display: flex;
justify-content: space-evenly;
align-items: center;
}
</style>
#### SPROUT Python 2025, Week 5
# Function
#### 講師:蔡政廷
---
## 什麼是函式
----
在數學中
函數(function)
是一個有輸入跟輸出的黑盒子
<div class="flex-row">

\begin{align}
f(x)&\overset{\mathrm{def}}{=}2x+3 \\
f(3)&=9 \\
f(7)&=17 \\
&\vdots
\end{align}
</div>
----
在程式中
函式(function)
也可以是數學中的函數…
```python
def f(x):
return 2*x + 3
print(f(3)) # 9
print(f(7)) # 17
```
----
…但也可以僅僅是一段程式碼
```python
def greet(name):
s = 'Hello, ' + name + '!'
print(s)
greet('Alice') # Hello, Alice!
greet('Bob') # Hello, Bob!
```
因此,程式中的函式
是更為籠統的「子程式」(subroutine)
---
## Python 函式基本語法
----
### 基本語法
```python
# 定義(define)函式
def 函式名稱(參數1, 參數2, ...): # 參數可以有 0 到很多個
# 你要做的事情
# ...
return 回傳值 # 不一定要有
# 使用╱呼叫(call)函式
函式名稱(引數1, 引數2, ...)
```
----
### 範例(再看一次)
```python
def f(x):
return 2*x + 3
print(f(3)) # 9
print(f(7)) # 17
```
* `f`:函式名稱
* `x`:參數(parameter)
* `2*x + 3`:回傳值
* `3`、`7`:引數(argument)
---
## 作用域
----
### 作用域
```python
def greet(name):
s = 'Hello, ' + name + '!'
print(s)
greet('Alice') # Hello, Alice!
greet('Bob') # Hello, Bob!
print(s) # NameError: name 's' is not defined
```
在函式內定義的變數
只作用於函式範圍內
----
### 更改參數的值
```python
def set_to_42(x):
x = 42
a = 7
set_to_42(a)
print(a) # 7 還是 42?
```
----
### (並沒有)更改參數的值
```python
def set_to_42(x): # 把 x 指到傳進來的參數值
x = 42 # 把 x 指到 42
a = 7
set_to_42(a)
print(a) # 7
```
在 Python 當中
有「賦值」(assign)
與「更改」(modify)
這兩種操作
「賦值」可以讓變數指向不同的值
「更改」可以更改變數指向的值
`int`、`string` 等不能被更改(immutable)
----
### 賦值與更改
Python 的 `list` 可以被更改(mutable)
```python
def assign(x):
x = [2025, 3, 23]
def modify1(x):
x[0] = 42
def modify2(x):
x.append(42)
a = [1, 2, 3]
assign(a)
print(a) # [1, 2, 3]
modify1(a)
print(a) # [42, 2, 3]
modify2(a)
print(a) # [42, 2, 3, 42]
```
---
## 實用語法
----
### 指定參數&回傳值型別
可以在定義函式時指定型別
但 Python 不會真的去檢查
所以比較像註解
```python
def f(x: int) -> int:
return 2*x + 3
print(f(3)) # 9
print(f(7)) # 17
print(f('Hello')) # 錯誤,但不是因為檢查了型別
```
----
### 回傳多個值
`return` 可以一次回傳多個值
用逗號隔開就好
呼叫時用一樣的順序拿值
```python
def min_max(n):
x = min(n)
y = max(n)
return x, y
n = [8, 4, 3, 7, 1]
a, b = min_max(n)
print(a) # 1
print(b) # 8
```
----
### 參數預設值
可以設定參數的預設值
呼叫時沒指定引數就用預設值
```python
def greet(name, time='morning'):
print(f'Good {time}, {name}!')
greet('John') # Good morning, John!
greet('John', 'evening') # Good evening, John!
greet('John', 'night') # Good night, John!
```
----
### 潛在問題?
如果前面的參數想用預設值
但後面的參數想自己指定
Python 根本看不出來啊!
```python
def greet(name, time='morning', mood='Good'):
print(f'{mood} {time}, {name}!')
greet('John') # Good morning, John!
# 想要:Bad morning, John!
greet('John', 'Bad') # Good Bad, John!
# 可以但很爛的解法,需要把前面的預設值抄下來
greet('John', 'morning', 'Bad') # Bad morning, John!
```
----
### 表明參數!
在呼叫函式的時候
直接指定「哪個參數=哪個引數」就好了
```python
def greet(name, time='morning', mood='Good'):
print(f'{mood} {time}, {name}!')
greet('John', mood='Bad') # Bad morning, John!
```
----
### 不定數目參數
在參數前面加個星號
呼叫時就可以把後面所有參數
包成一個 iterable
```python
def times_n(n, *x):
for i in x:
print(i*n)
times_n(2, 3) # 6
times_n(2, 1, 2, 3)
# 4
# 6
# 8
```
----
### 上課練習
[666. 哪裡買TELSA電動車卡便宜](https://tioj.sprout.tw/contests/16/problems/666)
[711. 比24還要好笑的笑話](https://tioj.sprout.tw/contests/16/problems/711)
---
## 遞迴
## Recursion
----
### 什麼是遞迴
函式可以呼叫自己
這個動作稱為遞迴
```python
def f():
f()
f() # RecursionError: maximum recursion depth exceeded
```
(蛤,這不是出錯了嗎)
----
### 中止條件
遞迴函式必須要有「中止條件」
比方說階乘:
$$
n! =
\begin{cases}
1 & \textrm{if } n = 0 \\
n \times (n-1)! & \textrm{otherwise} \\
\end{cases}
\quad
\forall n \in \mathbb N
$$
$0! = 1$ 就是中止條件
因為這個情況不用算其他的階乘
----
### 用 Python 實作階乘
```python
def f(n):
if n == 0:
return 1
return n * f(n - 1)
print(f(1)) # 1
print(f(2)) # 2
print(f(3)) # 6
print(f(4)) # 24
print(f(5)) # 120
```
----
### 遞迴的優勢
同樣的功能,用迭代也能做:
```python
def f(n):
product = 1
for i in range(1, n+1):
product *= i;
return product;
print(f(5)) # 120
```
但遞迴的優勢在於「絕佳的可讀性」
----
### 費氏數列
$$
\begin{gather}
f(x) =
\begin{cases}
1 & \textrm{if } x=0 \textrm{ or } x=1 \\
f(x-1) + f(x-2) & \textrm{otherwise}
\end{cases} \\
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, \dots
\end{gather}
$$
----
### Python 實作
<div class="flex-row">
<div>
迭代法:
```python
def f(x):
a = 1
b = 1
for i in range(x-1):
t = a
a = b
b = t + b
return b
print(f(7)) # 21
```
</div>
<div>
遞迴法:
```python
def f(x):
if x == 0 or x == 1:
return 1
return f(x-1) + f(x-2)
print(f(7)) # 21
```
</div>
</div>
使用遞迴時
你會發現自己根本只要照抄問題定義
---
## 頭等公民
----
在 Python 當中,函式是「頭等公民」
意思是函式可以:
* 存到變數裡
* 作為參數
* 作為回傳值
* 在函式裡定義
----
### 把函式存到變數裡
```python
def f(x):
return 2*x + 3
print(f(7)) # 17
g = f
print(g(7)) # 17
```
----
### 把函式當參數
範例:把運算「向量化」
```python
def vector_op(x: list, y: list, f) -> list:
z = []
for i in range(len(x)):
z.append(f(x[i], y[i]))
return z
def add(x, y):
return x + y
def mul(x, y):
return x * y
a = [1, 2, 3]
b = [4, 5, 6]
print(vector_op(a, b, add)) # [5, 7, 9]
print(vector_op(a, b, mul)) # [4, 10, 18]
```
----
### 在函式裡定義函式&作為回傳值
範例:發放消費券
```python
def create_coupon(multiplier):
def coupon(money):
return money * multiplier
return coupon
times3 = create_coupon(3)
times5 = create_coupon(5)
print(times3(1000)) # 3000
print(times5(1000)) # 5000
```
----
### Lambda 函式
可以用 `lambda` 來定義一些很短的函式
```python
# lambda 參數1, 參數2, ...: 回傳值
print((lambda x: 2*x + 3)(7)) # 17
```
Lambda 函式沒有名字
但你可以把它存到變數裡
然後用那個變數的名字
```python
f = lambda x: 2*x + 3
print(f(7)) # 17
```
----
### 把運算「向量化」,但是 lambda
```python
def vector_op(x: list, y: list, f) -> list:
z = []
for i in range(len(x)):
z.append(f(x[i], y[i]))
return z
a = [1, 2, 3]
b = [4, 5, 6]
print(vector_op(a, b, lambda x, y: x+y)) # [5, 7, 9]
print(vector_op(a, b, lambda x, y: x*y)) # [4, 10, 18]
```
----
### 發放消費券,但是 lambda
```python
create_coupon = lambda multiplier: lambda money: money * multiplier
times3 = create_coupon(3)
times5 = create_coupon(5)
print(times3(1000)) # 3000
print(times5(1000)) # 5000
```
---
# 下課
大作業加油~
### 其他習題
[761. 超級貓貓星際漫遊-3](https://tioj.sprout.tw/contests/16/problems/761)
{"slideOptions":"{\"transition\":\"slide\"}","title":"Function","contributors":"[{\"id\":\"6f837504-512a-4e38-b145-0e8a28bc74ab\",\"add\":8257,\"del\":1761}]","description":"在數學中函數(function)是一個有輸入跟輸出的黑盒子"}