跳到內容

函式

函式是把一段程式包起來、取個名字,之後要用就呼叫它,不必一直重寫。這一章看怎麼定義函式、傳資料進去、把結果拿回來。

用 def 定義函式:def 後面接名字和一對括號,底下縮排的就是函式的內容。定義好之後,寫「名字加括號」就能呼叫它:

def greet(name):
print("你好," + name)
greet("小明")

括號裡的 name 是參數,呼叫時給的 "小明" 會被塞進去。按「下一步」時注意,呼叫 greet 那一刻,右邊的呼叫堆疊會多出一層,跳進函式裡執行。

前面的 greet 只是印東西。更常見的是讓函式算出一個結果交回來,用的是 return:

def add(a, b):
return a + b
result = add(3, 5)
print(result)

add(3, 5) 執行到 return a + b,就把 8 交回呼叫的地方,存進 result。函式一旦 return,就結束、跳回原來的位置。

return 也可以一次回傳好幾個值,用逗號隔開;接收時同樣用逗號一次拆開:

def divide(a, b):
return a // b, a % b
quotient, remainder = divide(17, 5)
print("商是", quotient)
print("餘數是", remainder)

return a // b, a % b 一口氣把商和餘數都交回來,quotient, remainder = divide(17, 5) 再一次接住兩個。

參數可以給預設值。呼叫時沒提供,就用預設的;有提供,就用你給的:

def greet(name, greeting="你好"):
print(greeting + "," + name)
greet("小明")
greet("小美", "早安")

greet("小明") 沒給 greeting,用預設的「你好」;greet("小美", "早安") 則把預設值蓋掉。

定義時有一條規則:有預設值的參數要放在沒有預設值的後面。寫成 def greet(greeting="你好", name) 是不行的,Python 會直接報 SyntaxError。原因是呼叫時的值是從左邊開始填的,greet("小明") 會填到 greeting,反而讓 name 沒有值,這個預設值就永遠派不上用場。

先把兩個詞分清楚:定義函式時寫在括號裡的名字叫參數(parameter),像 describe(name, age) 裡的 name 和 age;呼叫時實際交進去的值叫引數(argument),像 describe("小明", 18) 裡的 "小明" 和 18。呼叫要做的事,就是把每個引數對到一個參數。Python 有兩種對法:靠位置,或靠名字。

def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", 18)
describe(age=20, name="小美")

describe("小明", 18) 沒有寫名字,Python 就照順序對:第一個引數給第一個參數 name,第二個給 age。這種叫位置引數。describe(age=20, name="小美") 用 參數名=值 的寫法直接指名,這種叫關鍵字引數,對的是名字,所以順序可以隨便排。步進時看右邊的呼叫堆疊,兩次呼叫裡 name 和 age 拿到的值都是對的。

位置引數:照順序,數量要剛好

Section titled “位置引數:照順序,數量要剛好”

位置引數的規則只有一條:第幾個引數,就給第幾個參數。Python 只認順序,不會管值合不合理,所以順序放反了它不會發現,只會給你一個怪怪的結果:

def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", 18)
describe(18, "小明")

第二次呼叫把 18 給了 name、"小明" 給了 age,印出「18 今年 小明 歲」。程式沒有報錯,這種錯只能靠自己看出來。

數量 Python 倒是會檢查:函式定義了幾個參數,位置引數就要給剛好幾個(有預設值的可以省略,後面會講)。少給,Python 會告訴你哪個參數沒拿到值:

# 最後一行故意寫錯,看 Python 會報什麼錯。
# type: ignore[ty:missing-argument]
def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", 18)
describe("小美")

describe("小美") 只給了一個,age 沒有值,程式停下來、報出 TypeError: describe() missing 1 required positional argument: 'age'。多給也一樣不行:

# 最後一行故意寫錯,看 Python 會報什麼錯。
# type: ignore[ty:too-many-positional-arguments]
def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", 18)
describe("小美", 20, 165)

describe 只有兩個參數,卻塞了三個值,第三個 165 沒地方放,報的是 TypeError: describe() takes 2 positional arguments but 3 were given。訊息裡直接寫了「定義幾個、給了幾個」,對照函式定義就能找到問題。

關鍵字引數的規則也只有一條:等號左邊要和定義裡的參數名一模一樣。名字對上了,順序就無所謂;名字對不上,就算只差一個字母,Python 也不會幫你猜:

# 最後一行故意寫錯,看 Python 會報什麼錯。
# type: ignore[ty:unknown-argument, ty:missing-argument]
def describe(name, age):
print(name, "今年", age, "歲")
describe(name="小明", age=18)
describe(name="小美", year=20)

describe 的參數叫 age,不叫 year,所以 year=20 對不到任何參數,報 TypeError: describe() got an unexpected keyword argument 'year'。看到「unexpected keyword argument」,就回去對照函式定義,通常是名字打錯,或記錯了參數叫什麼。

一次呼叫裡可以同時用兩種,最常見的寫法是前面幾個靠位置、後面幾個指名:

def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", age=18)
describe("小美", 20)
describe(name="小華", age=22)

describe("小明", age=18):"小明" 沒寫名字,照位置給第一個參數 name;age=18 指名給 age。混用時要守兩條規則。

第一條:位置引數要全部放在關鍵字引數前面。位置引數靠「第幾個」對應,一旦前面出現了指名的引數,後面再出現沒名字的值,Python 就數不出它是第幾個了。這條規則 Python 在執行前就會檢查,所以違反了是 SyntaxError,程式一步都不會跑:

def describe(name, age):
print(name, "今年", age, "歲")
describe(name="小明", 18)

describe(name="小明", 18) 先指名再給位置,報 SyntaxError: positional argument follows keyword argument。改成 describe("小明", age=18),或乾脆全部指名 describe(name="小明", age=18),就都可以。

第二條:每個參數只能收到一個值。前面已經用位置給過的參數,後面不能再用名字給一次:

# 最後一行故意寫錯,看 Python 會報什麼錯。
# type: ignore[ty:parameter-already-assigned, ty:missing-argument]
def describe(name, age):
print(name, "今年", age, "歲")
describe("小明", age=18)
describe("小美", name="小華")

describe("小美", name="小華") 裡,"小美" 照位置已經給了 name,name="小華" 又要再給一次,Python 不知道該用哪個,報 TypeError: describe() got multiple values for argument 'name'。

關鍵字引數最常用在有預設值的函式上。參數多、又大多有預設值時,你往往只想改其中一個:

def order(drink, size="中杯", ice="正常冰"):
print(size, ice, drink)
order("紅茶")
order("紅茶", "大杯", "去冰")
order("紅茶", ice="去冰")

order("紅茶", ice="去冰") 只改冰量,size 維持預設。如果只能用位置引數,就得寫成 order("紅茶", "中杯", "去冰"),把不想改的 "中杯" 也重打一次,才能把 "去冰" 排到第三個位置。另一個好處是可讀性:describe("小明", 18) 要回頭看定義才知道 18 是什麼,describe(name="小明", age=18) 一眼就懂。

  • 位置引數:照順序對應,第幾個給第幾個參數;數量要和參數一樣多,有預設值的可以省略。
  • 關鍵字引數:寫 參數名=值,名字要和定義一字不差;順序隨意。
  • 混用:位置引數全部在前、關鍵字引數全部在後,違反是 SyntaxError。
  • 每個參數只能拿到一個值:少給、多給、重複給都是 TypeError,訊息會告訴你是哪個參數出問題。

return 不一定要放在最後。一遇到就可以回傳、結束函式。這常拿來先處理特殊情況,把主要的邏輯留到後面,不用層層巢狀的 if:

def check_age(age):
if age < 0:
return "年齡不能是負的"
if age < 18:
return "未成年"
return "成年"
print(check_age(-5))
print(check_age(15))
print(check_age(30))

年齡是負的就直接回傳、不再往下;否則再看是不是未成年;都不是,才走到最後的「成年」。

接收任意多個引數:*args 與 **kwargs

Section titled “接收任意多個引數:*args 與 **kwargs”

有時你不知道會傳幾個引數進來。參數前面加一個 *,就會把所有位置引數收集成一個元組(tuple)。元組是一排不能改的資料,「元組、集合、字典」那章會詳細講,這裡只用到「能用 for 走過它」這件事:

def total(*numbers):
result = 0
for n in numbers:
result = result + n
return result
print(total(1, 2, 3))
print(total(10, 20, 30, 40))

total(1, 2, 3) 和 total(10, 20, 30, 40) 傳的個數不同,函式裡的 numbers 都能一次收下、用迴圈加總。

加兩個 *(**)則是把關鍵字引數收集成一個字典(dictionary),以參數名當鍵、對應的引數當值:

def show_info(**info):
for key in info:
print(key, ":", info[key])
show_info(name="小明", age=18)

info[key] 是用鍵取出對應的值,字典那章會講。呼叫時的 name 和 age 都沒有寫在函式定義裡,info 全收下了。參數名 args、kwargs 只是慣例,換成別的名字也行,重點是前面的 * 和 **。

函式可以呼叫自己,這叫遞迴。關鍵是要有個停下來的條件,否則會無止盡地叫下去。下面用遞迴算階乘(5 的階乘是 5×4×3×2×1):

def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5))

一直按「下一步」,看右邊的呼叫堆疊怎麼一層一層疊上去:factorial(5) 要先算 factorial(4),factorial(4) 又要 factorial(3)……一路到 factorial(0) 回傳 1,才逐層乘回來,最後得到 120。這個「堆疊疊起來又收回去」的過程,用步進看最清楚。

v0.9.1 (c924b5b)