自訂函式
雖然 Python 自訂函式的結構和其他語言大同小異(函式名稱、參數與回傳值),但是語法和 Java 非常的不同。def func_name(): clause最大不同在於沒有使用大括號指定函式的範圍,而是用縮排,以下逐點說明與 Java 的差異。
- 使用關鍵字 def 表示開始自訂函式
- 接下來是函式名稱搭配小括號與參數,這部份和 Java 類似,只是不用宣告參數型別
- 函式名稱的命名規則和變數命名規則一樣。
- 避免變數和函式同名
- 小括號之後是冒號,表示名稱宣告結束,進入函式內容(clause)
- 所有的函式內容必須縮排
- 函式的結束方式在 interactive mode 與 script mode 不同
- interactive mode:一空白行
- script mode:結束縮排
Interactive mode
>>> def sayHello(name): ... print('Hello ' + name); ... >>> sayHello <function sayHello at 0x037A3738> >>> sayHello('Neil') Hello Neil >>> type(sayHello) <class 'function'>在 Interactive mode 冒號後按下換行,會自動出現三點,表示進入函式內容,但還是須外額外的縮排,否則會報錯。
Script mode
def sayHello(name): print('Hello ' + name) sayHello('Neil')所有的 function 都會有 return,如果沒有明確加上 return,Python 會自動加上 return None,或者只用了 return,Python 也會將它變成 return None。
None
None 值是 NoneType 這個資料類型唯一的值,代表沒有值,像是 Java 的 null 或者 Javascript 的 undefined。可以用比較運算子 == 比對出 None。
print() 就是 return None。
呼叫前定義
函式的定義必須在呼叫之前,否則得到 NameError。sayHello('Neil') # NameError def sayHello(name): print('Hello ' + name)
Method
Python 函式還有另一種稱為 Method 的型態,即與特定資料類型綁在一起的函式,例如 'str'.islower()。參數
Python 函式的參數非常的強,強到一個有點複雜的狀態。Required & Optional Arguments
在定義函式時,參數可以分成兩類:必要(required)與非必要(optional)。def f(name, food = 'Banana'): print(name + ' eats ' + food) f('Neil') # Neil eats Banana f('Neil', 'Apple') # Neil eats Applename 是必要的,food 是非必要的。
特別的是 Optional argument 的值可以使用 expression。
todaysSelection = 'Avocado' def f(name, food = todaysSelection): print(name + ' eats ' + food) todaysSelection = 'Cherry' f('Neil') # Neil eats Avocado但要特別注意的是,Optional argument 的 expression 是在定義時求值(Avocade),而不是執行時(Cherry)!
由於 Optional argument 的 expression 是在定義時求值,所以只會求值一次,然後就存在該函式物件中,這個特色在參數值是 mutable 時,例如 list 或者 dict,有非常可怕副作用。
def f(name, food, foods = []): foods.append(food) print(name + ' eats ' + str(foods)) f('Neil', 'Coconut') # Neil eats ['Coconut'] f('Neil', 'Gragefruit') # Neil eats ['Coconut', 'Gragefruit'],憑空變出椰子來安全的作法是不要在 Optional argument 裡建立 mutable 物件。
def f(name, food, foods = None): if foods == None: foods = [] foods.append(food) print(name + ' eats ' + str(foods)) f('Neil', 'Coconut') # Neil eats ['Coconut'] f('Neil', 'Gragefruit') # Neil eats ['Gragefruit']
Positional & Keyword Arguments
在呼叫函式時,參數又可以分成兩類:位置(positional)與關鍵字(keyword)。有加上名稱的參數是 keyword argument,例如 name = 或 food =,沒上名稱的就是 positional argument。
f('Neil', food = 'Pineapple') # Neil eats Pineapple,一個 P、一個 K f('Neil', 'Melon') # Neil eats Melon,兩個 P f(name = 'Neil', food = 'Pear') # Neil eats Pear,兩個 K f(food = 'Guava', name = 'Neil') # Neil eats Guava,兩個 K,K 不用遵守定義的位置 f(food = 'Durian', 'Neil') # 一個 P、一個 K,Syntax error,P 不可以放在 K 的後面 f(food = 'Grape', food = 'Raisin') # 兩個 K,Syntax error,K 不能重複 f('Neil', name = 'Nail') # 一個 P、一個 K,TypeError,P 與 K 不能重複規則整理如下:
- Required argument 一定要給,不管是用 Positional 或者 Keyword argument 的方式。
- Optional argument 沒給就是用預設值。
- 用哪一種方式指定參數(Positional 或 Keyword argument),與參數是 Required 或者 Optional 是沒有關聯或限制的。
- Keyword argument 一定要放在 Positional argument 後面。
- 不能使用未定義的 Keyword argument。
- Keyword argument 的順序沒關係。
- Positional 或 Keyword argument 都不能重複傳入。
可變參數 Arbitrary Argument Lists - Gather 聚集參數
可以用 * 表示不確定的傳入參數數量,在函式裡得到的 args 為將所有參數打包起來的 tuple。def f1(*args): print(args) # ('a', 'b', 'c'),tuple of argus return ', '.join(args) print(f1('a', 'b', 'c')) # a, b, c在可變參數之前可以使用正常參數,例如 Required 或 Optional。
def f2(first, second = 'B', *args): print(args) # ('c', 'd') or () return first + ' : ' + second + ' - ' + ', '.join(args) print(f2('a', 'b', 'c', 'd')) # a : b - c, d但是在可變參數之前使用 Optional argument 會有些困擾。
只能像這樣,同時不給 Optional argument 與可變參數,或者只給 Optional argument。
print(f2('a')) # a : B - print(f2('a', 'b')) # a : b -由於位置的關係,不能只給可變參數,卻不給 Optional argument,因為參數會優先傳給 Optional argument,剩下的才是留給可變參數。
可以在最後面加上 Keyword argument 餵給 Optional argument 得到證明,因為會丟出參數重複的錯誤。
print(f2('a', 'b', 'c', second = 'd')) # TypeError,second 重複所以同時使用 Optional argument 與可變參數的解法是,把 Optional argument 放在最後面,在呼叫時,除非明確使用 Keyword argument 餵給 Optional argument,否則 Optional argument 都是用預設值。
def f3(first, *args, last = 'L'): print(args) # ('b', 'c', 'd') return first + ' : ' + last + ' - ' + ', '.join(args) print(f3('a', 'b', 'c', 'd')) # a : L - b, c, d print(f3('a', 'b', 'c', 'd', last = 'X')) # a : X - b, c, d
解開參數 Unpacking Argument Lists - Scatter 分散參數
這是上面可變參數的相反,可變參數是將多個變數組成一個 tuple 傳進去,解開參數是一個 list 或 tuple 解開後依序傳進去。以吃兩個 int 參數(起與迄)的 range() 為例,可以直接傳入一個帶有兩個 int item 的 list 或者 tuple,只要在變數前面加上 *,它就會自己「炸開」成兩個 int 參數。
print(list(range(2, 5))) # [2, 3, 4] # print(list(range([2, 5]))) # TypeError,無法將 list 轉成 range 需要的 int print(list(range(*[2, 5]))) # list 炸開,[2, 3, 4] print(list(range(*(2, 5)))) # tuple 炸開,[2, 3, 4] print(list(range(*'25'))) # TypeError,字串炸開來還是字串(字元),不會自動轉成 range 需要的 int那 dict 炸開會發生什麼事?
dict 要用 ** 來炸,炸開後以 key 對應函式的 keyword argument 將 value 傳入。
如果不小心用 * 來炸,是將 key 依序傳入,與上面 * 炸開 list 或 tuple 一樣。
def f4(a, b, c): print(a, b, c) f4('A', 'B', 'C') # f4({'a': 'A', 'b': 'B', 'c': 'C'}) # TypeError,dict 不能轉成 str f4(**{'a': 'A', 'b': 'B', 'c': 'C'}) # A B C,兩顆 * 炸開是得到 key-value pair f4(*{'a': 'A', 'b': 'B', 'c': 'C'}) # a b c,一顆 * 炸開是得到 key # f4(**{'a': 'A', 'b': 'B'}) # TypeError,少一個參數 # f4(**{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D' }) # TypeError,多一個參數 def f5(a = 'A', b = 'B', c = 'C'): print(a, b, c) f5(**{'a': 'A', 'b': 'B'}) # A B C,少一個參數對 Optional argument 不是問題
是不是還少一個 ** ?
在腦袋也炸開之前,來看最後一個可能,呼叫函式時,可以用 * 炸開 list 與 tuple(甚至 dict),也可以用 ** 炸開 dict,而定義函式時,可以用 * 收即可變參數,那定義參數時,可以用 ** 嗎?無極限的 Python 說:當然可以!
def f6(a, *args, **d): print('Normal argument - ' + a); for a in args: print('Arbitrary argument - ' + a) for k in d: print('keyword argument - ' + k + ' : ' + d[k]) f6('N', 'AA1', 'AA2', 'AA3', KA1 = 'ka1', KA2 = 'ka2', KA3 = 'ka3') # Normal argument - N # Arbitrary argument - AA1 # Arbitrary argument - AA2 # Arbitrary argument - AA3 # keyword argument - KA1 : ka1 # keyword argument - KA2 : ka2 # keyword argument - KA3 : ka3* 負責收集沒人要的 Positional argument,** 負責收集沒人要的 Keyword argument。
函式的 docstring
在 def 下一行可以定義 docstring,用一個引號或三個引號都可以,但不能使用 f_string,還可以用 __doc__ 取得函式的 docstring 唷。def foo(): "docstring...." print(foo.__doc__) # docstring.... def bar(): """docstring....""" print(bar.__doc__) # docstring.... def nope(): f"docstring...." print(nope.__doc__) # None---
---
---
沒有留言:
張貼留言