資料結構搞定後,這篇把流程控制(if / match / for / while)跟函式(預設值、*args、/、*、closure、lambda)一次串完。重點放在 Python 特有的設計:可變預設值的陷阱、限定參數種類的 / 與 *、if __name__ == '__main__' 這個慣用 pattern。
if / elif / else
n = 5
if n > 0:
print('positive')
elif n == 0:
print('zero')
else:
print('negative')
三元表達式:
label = 'positive' if n > 0 else 'non-positive'
match(Python 3.10+)
結構化的 pattern matching:
def describe(x):
match x:
case 0:
return 'zero'
case int() if x < 0:
return 'negative'
case [a, b]:
return f'pair {a},{b}'
case {'kind': 'circle', 'r': r}:
return f'circle r={r}'
case _:
return 'other'
不只能比對值,還能解構 list / dict、判斷型別、加 guard 條件。
for 與 while
for 走訪可迭代物件:
for x in [1, 2, 3]:
print(x)
# range
for i in range(5): # 0..4
print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8
print(i)
# 同時拿 index
for i, v in enumerate(['a', 'b', 'c']):
print(i, v)
# 同時走訪多個
for a, b in zip([1, 2, 3], ['a', 'b', 'c']):
print(a, b)
# 反向
for x in reversed([1, 2, 3]):
print(x)
while:
n = 3
while n > 0:
print(n)
n -= 1
break / continue:
for x in range(10):
if x == 3:
continue
if x == 7:
break
print(x)
for / while 的 else
迴圈正常結束(沒有被 break)才會執行 else:
for x in [1, 2, 3]:
if x == 99:
break
else:
print('not found') # 會印
實務不太常用,看到不要嚇到。
函式
def add(a, b):
return a + b
# 預設值
def greet(name, greeting='Hello'):
return f'{greeting}, {name}'
greet('Jeremy')
greet('Jeremy', 'Hi')
greet(name='Jeremy', greeting='Hi') # 具名參數
greet(greeting='Hi', name='Jeremy') # 順序可換
預設值「定義時就建立並共用」,所以可變物件當預設值會踩雷:
def append(item, lst=[]): # ❌ 同一個 list 被所有呼叫共用
lst.append(item)
return lst
append(1) # [1]
append(2) # [1, 2] ← 不是 [2]!正確寫法:用 None 哨兵:
def append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst*args 與 **kwargs
收集任意數量的位置參數與關鍵字參數:
def f(*args, **kwargs):
print(args) # tuple
print(kwargs) # dict
f(1, 2, 3, name='Jeremy', age=30)
# args = (1, 2, 3)
# kwargs = {'name': 'Jeremy', 'age': 30}
呼叫時也能用 * / ** 拆包:
nums = [1, 2, 3]
def add3(a, b, c): return a + b + c
add3(*nums) # 等同 add3(1, 2, 3)
opts = {'name': 'Jeremy', 'age': 30}
def show(name, age): print(name, age)
show(**opts)
限定參數種類:/ 與 *
def f(a, b, /, c, d, *, e, f):
pass
# ^ ^
# / *
# a, b 必須位置傳;c, d 兩種都行;e, f 必須具名傳
f(1, 2, 3, 4, e=5, f=6) # ✅
f(1, 2, c=3, d=4, e=5, f=6) # ✅
f(a=1, b=2, c=3, d=4, e=5, f=6) # ❌ a, b 不能具名
f(1, 2, 3, 4, 5, 6) # ❌ e, f 不能位置傳
設計對外 API 時很有用:把容易搞混的參數鎖成只能具名傳。
return
沒寫 return 或寫 return 不帶值,預設回傳 None:
def noop():
pass
print(noop()) # None
回傳多個值就 tuple:
def split(s):
return s.split('@')
local, domain = split('a@b.com')
lambda(匿名函式)
只能寫一個表達式:
square = lambda x: x * x
square(3) # 9
sorted([(1, 'b'), (3, 'a'), (2, 'c')], key=lambda x: x[1])
# [(3, 'a'), (1, 'b'), (2, 'c')]
只在「需要短小函式作為值」的場景用(sort key、map / filter)。複雜邏輯一律寫 def。
函式是一等公民
def add(a, b): return a + b
def mul(a, b): return a * b
ops = {'+': add, '*': mul}
ops['+'](2, 3) # 5
# 傳給其他函式
def apply(fn, x, y):
return fn(x, y)
apply(add, 1, 2)
巢狀函式與 closure
def make_counter():
n = 0
def inc():
nonlocal n # 不寫 nonlocal 會被當成新區域變數
n += 1
return n
return inc
c = make_counter()
c() # 1
c() # 2
nonlocal 指向外層(非 global)作用域,global 指向最外層全域作用域。
沒有 nonlocal 會怎樣?n += 1 等同於 n = n + 1,Python 看到賦值就把 n 當成「新的區域變數」,但右側讀 n 時還沒定義——觸發 UnboundLocalError。nonlocal 明確告訴 Python「這個 n 是外層作用域的那個」。
docstring
函式第一行寫字串就會變成 docstring,被 help() 讀取:
def add(a, b):
"""Return the sum of a and b."""
return a + b
help(add)
一個常見的 main pattern
def main():
# 真正的程式邏輯
...
if __name__ == '__main__':
main()
這個 .py 被直接執行時,__name__ 是 '__main__';被 import 時則是模組名。所以這段程式只在直接執行時才跑,被 import 時不會。
走 functional 風格
# map / filter(會回傳 iterator)
list(map(lambda x: x * 2, [1, 2, 3])) # [2, 4, 6]
list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4])) # [2, 4]
# 等價的 comprehension(更 pythonic)
[x * 2 for x in [1, 2, 3]]
[x for x in [1, 2, 3, 4] if x % 2 == 0]
# reduce 要 import
from functools import reduce
reduce(lambda a, b: a + b, [1, 2, 3, 4]) # 10
# 累積過程:a=1, b=2 → 3;a=3, b=3 → 6;a=6, b=4 → 10
慣例:能寫 comprehension 就寫 comprehension,可讀性比 map / filter 好。
下一篇進物件導向,這篇的函式概念會延伸到 method、self、classmethod 與 staticmethod,順便把 dataclass 一次帶過。
Latest Updates
- 2026.06.11 Content updated
