函式是一段可重複使用的程式碼,用 def 關鍵字定義,呼叫時執行函式內的程式,達到「寫一次、多次使用」的效果。
Python 的函式支援預設參數、關鍵字引數、可變參數,以及作為一級物件傳遞與回傳。
定義與呼叫函式#
1
2
3
4
5
6
7
| # 定義函式
def greet(name):
print(f"Hello, {name}!")
# 呼叫函式
greet("Alice") # Hello, Alice!
greet("Bob") # Hello, Bob!
|
回傳值(return)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| def add(a, b):
return a + b
result = add(3, 5)
print(result) # 8
# 可以回傳多個值(實際上回傳的是 tuple)
def min_max(numbers):
return min(numbers), max(numbers)
lo, hi = min_max([3, 1, 4, 1, 5, 9])
print(lo, hi) # 1 9
# 沒有 return 或 return 無值時,回傳 None
def do_nothing():
pass
result = do_nothing()
print(result) # None
|
參數類型#
位置參數(Positional Arguments)#
1
2
3
4
| def describe(name, age, city):
print(f"{name},{age} 歲,住在 {city}")
describe("Alice", 25, "Taipei") # 按順序傳入
|
預設參數(Default Arguments)#
1
2
3
4
5
6
| def greet(name, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice") # Hello, Alice!
greet("Bob", "Hi") # Hi, Bob!
greet("Charlie", greeting="Hey") # Hey, Charlie!
|
注意 :預設參數必須放在非預設參數的 後面 。
關鍵字參數(Keyword Arguments)#
1
2
3
4
5
| def describe(name, age, city):
print(f"{name},{age} 歲,住在 {city}")
# 用關鍵字傳入,順序可以不同
describe(age=25, city="Taipei", name="Alice")
|
*args:接受任意數量的位置參數#
當你不確定呼叫者會傳入幾個參數時,用 * 把多餘的位置參數打包成一個 tuple 。args 只是慣用名稱,重點是前面的 *,寫成 *nums、*items 都可以。
1
2
3
4
5
6
7
8
| def total(*args):
print(args) # args 是 tuple
print(type(args)) # <class 'tuple'>
return sum(args)
print(total()) # 0 (傳 0 個 → args = ())
print(total(1, 2, 3)) # 6 (args = (1, 2, 3))
print(total(1, 2, 3, 4, 5)) # 15 (args = (1, 2, 3, 4, 5))
|
對照沒有 * 的版本就會看出差別:
1
2
3
4
5
| def total_fixed(a, b, c): # 只能剛好傳 3 個
return a + b + c
total_fixed(1, 2) # ❌ TypeError:少一個參數
total_fixed(1, 2, 3, 4) # ❌ TypeError:多一個參數
|
**kwargs:接受任意數量的關鍵字參數#
** 把多餘的「name=value 形式的引數」打包成一個 dict ,key 是參數名(字串),value 是傳入的值。同樣,kwargs 只是慣用名稱。
1
2
3
4
5
6
7
8
9
10
11
| def print_info(**kwargs):
print(kwargs) # kwargs 是 dict
print(type(kwargs)) # <class 'dict'>
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25, city="Taipei")
# {'name': 'Alice', 'age': 25, 'city': 'Taipei'}
# name: Alice
# age: 25
# city: Taipei
|
*args 收到的是 tuple(只有值、有順序),**kwargs 收到的是 dict(有名字、可用 key 取值),兩者解決的是不同情境。
反向操作:用 * / ** 拆開傳入#
* 和 ** 在 呼叫函式時 反過來用,可以把現有的 tuple/list/dict 「拆開」成個別參數:
1
2
3
4
5
6
7
8
9
10
| def describe(name, age, city):
print(f"{name},{age} 歲,住在 {city}")
# 把 list/tuple 拆成位置參數
data = ["Alice", 25, "Taipei"]
describe(*data) # 等同 describe("Alice", 25, "Taipei")
# 把 dict 拆成關鍵字參數
info = {"name": "Bob", "age": 30, "city": "Tokyo"}
describe(**info) # 等同 describe(name="Bob", age=30, city="Tokyo")
|
口訣:定義函式時 用 * / ** 是「收集」,呼叫函式時 用 * / ** 是「展開」。
組合使用#
四種參數放在一起時,順序固定為:位置參數 → *args → 關鍵字參數 → **kwargs 。
1
2
3
4
5
6
7
8
9
10
11
| def flexible(required, *args, default="hello", **kwargs):
print(f"必要參數:{required}")
print(f"額外位置參數:{args}")
print(f"預設參數:{default}")
print(f"額外關鍵字參數:{kwargs}")
flexible("必填", 1, 2, 3, default="world", x=10, y=20)
# 必要參數:必填
# 額外位置參數:(1, 2, 3)
# 預設參數:world
# 額外關鍵字參數:{'x': 10, 'y': 20}
|
注意:*args 後面的所有參數(如上例的 default)都變成 只能用關鍵字傳入 ,因為位置已經被 *args 吃光了。
最常見的應用是 包裝其他函式 ——不在意參數內容、原封不動轉手:
1
2
3
4
5
6
7
8
9
10
11
12
| def logged(func):
def wrapper(*args, **kwargs):
print(f"呼叫 {func.__name__},參數:{args}, {kwargs}")
return func(*args, **kwargs) # 原樣轉發
return wrapper
@logged
def add(a, b):
return a + b
add(3, 5)
# 呼叫 add,參數:(3, 5), {}
|
變數作用域(Scope)#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| x = 10 # 全域變數
def foo():
x = 20 # 局部變數,不影響全域的 x
print(x) # 20
foo()
print(x) # 10
# 若要在函式內修改全域變數,用 global
def bar():
global x
x = 30
bar()
print(x) # 30
|
Lambda 函式(匿名函式)#
Lambda 是一種簡短的 單行函式 ,常搭配 sorted()、map()、filter() 使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 語法:lambda 參數: 表達式
square = lambda x: x ** 2
print(square(5)) # 25
add = lambda a, b: a + b
print(add(3, 4)) # 7
# 搭配 sorted()
words = ["banana", "apple", "cherry", "kiwi"]
sorted_words = sorted(words, key=lambda w: len(w))
print(sorted_words) # ['kiwi', 'apple', 'banana', 'cherry']
# 搭配 sorted() 對字典串列排序
students = [
{"name": "Alice", "score": 85},
{"name": "Bob", "score": 92},
{"name": "Charlie", "score": 78},
]
by_score = sorted(students, key=lambda s: s["score"], reverse=True)
for s in by_score:
print(f"{s['name']}: {s['score']}")
|
常用高階函式#
高階函式(Higher-Order Function)是指接受「函式」作為參數的函式。Lambda 最常見的用途就是作為這類函式的引數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# map():對每個元素套用函式
squares = list(map(lambda x: x ** 2, numbers))
print(squares) # [1, 4, 9, 16, 25]
# filter():篩選滿足條件的元素
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# sorted():自訂排序規則
words = ["banana", "apple", "cherry", "kiwi"]
by_length = sorted(words, key=lambda w: len(w))
print(by_length) # ['kiwi', 'apple', 'banana', 'cherry']
|
map()、filter()、sorted() 的完整說明(多序列、多條件排序、進階用法)請見 CH11:常用內建函式。
閉包(Closure)#
函式可以記住外部函式的變數:
1
2
3
4
5
6
7
8
9
10
| def make_multiplier(factor):
def multiplier(x):
return x * factor # 記住了 factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
|
遞迴(Recursion)#
函式可以呼叫自己:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120(5 × 4 × 3 × 2 × 1)
# 費波那契數列
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
for i in range(10):
print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34
|
實戰範例#
計算移動平均#
1
2
3
4
5
6
7
8
9
10
11
| def moving_average(prices, window):
"""計算移動平均線"""
result = []
for i in range(len(prices) - window + 1):
avg = sum(prices[i:i + window]) / window
result.append(round(avg, 2))
return result
prices = [100, 102, 98, 105, 110, 108, 112]
ma3 = moving_average(prices, 3)
print(ma3) # [100.0, 101.67, 104.33, 107.67, 110.0]
|
快速排序#
1
2
3
4
5
6
7
8
9
10
11
| def quicksort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quicksort(left) + middle + quicksort(right)
data = [3, 6, 8, 10, 1, 2, 1]
print(quicksort(data)) # [1, 1, 2, 3, 6, 8, 10]
|