5.function#
function#
所謂function是接收一些輸入,然後做一些事情,最後吐出輸出。
自定義function,對於資料分析來說非常實用。 不僅在資料處理與分析的過程中,可以避免一直重複複製貼上同一段語法。 也可以有效地組織你的程式,讓程式的結構更清楚,更好維護。
定義function#
要使用
def
關鍵字為function命名
function名稱後面接著一個
()
,裡面放「參數(parameter)」,也就是function的輸入。()
後面接著一個冒號下方縮排4個空格,就是function中處理邏輯的區塊。
最後一行是
return
關鍵字,設定要輸出的東西。
def my_sum(a, b, c):
result = a + b + c
return result
使用function#
實際呼叫function時,只要在function的
()
中放入function需要的輸入,就可以執行了。呼叫時傳入的東西稱作引數(argument),在定義時則稱作參數(parameter)。
位置引數(positional argument)
不指定參數名稱直接呼叫函數的話,稱作位置引數(positional argument):
my_sum(1, 2, 3)
6
關鍵字引數(keyword argument)
如果指定參數名稱的話,稱作關鍵字引數(keyword argument):
my_sum(a=1, b=2, c=3)
6
位置引數跟關鍵字引數可以混用,但位置引數必須在關鍵字引數前面:
# 合法
my_sum(1, 2, c=3)
# 不合法
my_sum(a=1, 2, 3)
Cell In[4], line 5
my_sum(a=1, 2, 3)
^
SyntaxError: positional argument follows keyword argument
任意引數:*args
& **kwargs
當想要function可以傳入不固定的參數數量時,可以使用可變動參數。
其中*args
代表位置引數,**kwargs
代表關鍵字引數。
*args
用法範例如下:
def my_sum(*args):
result = 0
for number in args:
result += number
return result
my_sum(1,2,3,4,5)
15
my_sum(1,2,3,4,5,6,7,8,9,10)
55
**kwargs
用法範例如下:
def my_func(**kwargs):
for k, v in kwargs.items():
print(f'{k} -> {v}')
return None
my_func(a=1, b=2, c=3)
a -> 1
b -> 2
c -> 3
my_func(a=1, b=2, c=3, d=4, e=5)
a -> 1
b -> 2
c -> 3
d -> 4
e -> 5
多種參數可以混用:
def my_func(a, b, *args, **kwargs):
print(f'I am standard arg: {a} & {b}')
print(f'I am positional arg: {args}')
print(f'I am keyword arg: {kwargs}')
return None
my_func(1, 2, c=3)
I am standard arg: 1 & 2
I am positional arg: ()
I am keyword arg: {'c': 3}
my_func(1, 2, 3, d=4, e=5)
I am standard arg: 1 & 2
I am positional arg: (3,)
I am keyword arg: {'d': 4, 'e': 5}
由上面的例子可以看到,*args
是傳入一個tuple,**kwargs
則是傳入一個dict。
參數預設值(default parameter)#
可以在function定義時,指定參數的預設值,使用該function時就可以不必輸入引數傳入。
def my_sum(a, b, c=3):
result = a + b + c
return result
my_sum(4, 5)
12
my_sum(4, 5, 6)
15
注意,有指定預設值的參數必定是在位置引數之後,否則會搞不清楚引數傳入的順序。
# 不合法
def my_sum(a=1, b, c):
result = a + b + c
return result
Cell In[14], line 2
def my_sum(a=1, b, c):
^
SyntaxError: non-default argument follows default argument
可變物件作為參數預設值
以可變物件作為參數預設值可能會產生問題。 例如以下程式,假設我們希望這個function當沒有指定list時就初始化一個空的list,然後就把元素append進去:
def append_element(e, mylist=[]):
mylist.append(e)
return mylist
執行第一次,輸出了一個具有單一元素’a’
的list,符合預期:
append_element(e='a')
['a']
但執行第二次時,理論上沒有傳入一個list的話,
我們預期這個function應該是先產生一個空的list然後再append元素。
但實際上的結果是保留了上一次append進去的元素’a’
:
append_element(e='b')
['a', 'b']
之所以會這樣的原因是,function的預設值在定義階段就會被創建,後續使用時不會再重新創建。
所以第一次執行function時,mylist
就已經被建立,後續重新呼叫function並不會再重新創建。
又因為list是一種可變物件,可以不斷把東西塞進去。
可以看到以下程式,每次呼叫時,mylist
的id都一樣,代表物件沒有被重新創建。
def append_element(e, mylist=[]):
mylist.append(e)
print(id(mylist))
return mylist
append_element(e='a')
4390319424
['a']
兩次的id是一樣的。
append_element(e='b')
4390319424
['a', 'b']
解決方法是用None
代替空的可變物件。
def append_element(e, mylist=None):
if mylist is None:
mylist = []
mylist.append(e)
print(id(mylist))
return mylist
append_element(e='a')
4390350336
['a']
可以看到不會保留上一次執行時append進去的元素了,而且兩個id是不樣的,代表物件每次呼叫時有被重新創建。
append_element(e='b')
4390347648
['b']
特殊參數#
僅限位置參數 (Positional-Only Parameters)
只要在參數中加入”/”就可以限制”/”前的引數都必須是位置引數。
def pos_only_func(a, b, c, /):
print(a, b, c)
# 合法
pos_only_func(1, 2, 3)
# 不合法
pos_only_func(1, 2, c=3)
1 2 3
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[23], line 5
2 pos_only_func(1, 2, 3)
4 # 不合法
----> 5 pos_only_func(1, 2, c=3)
TypeError: pos_only_func() got some positional-only arguments passed as keyword arguments: 'c'
僅限關鍵字引數 (Keyword-Only Arguments)
只要在參數中加入”*” 就可以限制 “*”後的引數都必須是關鍵字引數。
def kwd_only_func(*, f, g):
print(f, g)
# 合法
kwd_only_func(f=6, g=7)
# 不合法
kwd_only_func(6, g=7)
6 7
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[25], line 5
2 kwd_only_func(f=6, g=7)
4 # 不合法
----> 5 kwd_only_func(6, g=7)
TypeError: kwd_only_func() takes 0 positional arguments but 1 positional argument (and 1 keyword-only argument) were given
兩者可以混用:
def combined_func(a, b, c, /, d, e, *, f, g):
print(a, b, c, d, e, f, g)
combined_func(1, 2, 3, 4, e=5, f=6, g=7)
1 2 3 4 5 6 7
此外,*args
後面的參數也都是僅限關鍵字引數(Keyword-Only Arguments)。
def example_func(*args, c, d):
print(args)
print(c, d)
# 合法
example_func(1, 2, c=3, d=4)
(1, 2)
3 4
# 不合法
example_func(1, 2, 3, 4)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[29], line 2
1 # 不合法
----> 2 example_func(1, 2, 3, 4)
TypeError: example_func() missing 2 required keyword-only arguments: 'c' and 'd'
匿名函數:lambda運算式#
基本操作#
之所以稱做匿名函數是因為lambda運算式不需要定義function的名稱就可以使用。
例如以下是一個取平方的function:
def square(x):
return x**2
如果用lambda運算式來寫的話,如以下:
lambda x: x**2
<function __main__.<lambda>(x)>
呼叫的方式:
(lambda x: x**2)(3)
9
實用的情境#
假設一個list如下,如何根據tuple第二位來排序?
a_list = [('b', 1), ('C', 5), ('a', 2), ('c', 4), ('B', 3), ('A', 6)]
一般的sorted
會根據第一個元素來排序,元素的資料型別是str
的話,就會根據字母順序來排序(大寫在前)。
sorted(a_list)
[('A', 6), ('B', 3), ('C', 5), ('a', 2), ('b', 1), ('c', 4)]
可以用sorted()
的參數key
搭配lambda function:
sorted(a_list, key=lambda e: e[1])
[('b', 1), ('a', 2), ('B', 3), ('c', 4), ('C', 5), ('A', 6)]
不使用lambda的話就是要寫比較多的code,而且並不這麼一目了然:
def get_second_element(data: tuple):
return data[1]
sorted(a_list, key=get_second_element)
[('b', 1), ('a', 2), ('B', 3), ('c', 4), ('C', 5), ('A', 6)]
定義function的好習慣#
動態型別(dynamic types)與靜態型別(static type)#
在python中,變數的資料型別是 動態型別(dynamic types) ,意思是:
定義變數時不需要宣告變數的資料型別,而是直接賦值就可以了,而且,
如果要改變型別只要重新賦值即可。
相反地,所謂 靜態型別(static type) ,就是指變數賦值前必須先宣告好資料型別,而且一旦宣告後,除非重新宣告其他型別,否則用其他型別資料賦值就會產生錯誤。
myvar = 1
myvar = '1'
myvar = True
這樣感覺起來似乎動態型別方便多了!不必像靜態型別一樣有許多限制。事實上,限制帶來的是穩定性與可預測性,比較能夠避免程式出現意料之外的錯誤。
因此,雖然python是動態型別,但仍創造了所謂 型別提示(type hint) 的功能。使用方式如下:
is_student: bool = True
age: int = 24
a_list: list[str] = ['a','b','c'] # python 3.9以上支援
a_set: set[int] = {1, 2} # python 3.9以上支援
事實上 型別提示(type hint) 只是提示,python不會對type hint做任何事,所以即使用錯了型別也不會報錯。
雖然python不強制,但其實有套件可以替我們做檢查(例如mypy)。
檢查的方式例如,明明myvar
的type hint是str
型別,
但卻在程式某個地方對myvar
使用了.append()
方法等等。
總而言之,使用**型別提示(type hint)**可以讓程式更可讀也更好維護。
在函式定義中使用型別提示(type hint)#
直接在參數後面寫入資料型別
->
後面代表的是return的資料型別。
def lucky_number(name: str, birthday: str = '19000101') -> int:
sum_n = 0
for n in birthday:
sum_n += int(n)
return sum_n + len(name)
詳細入門介紹請參考:
Documentation#
“Code is more often read than written.”
— Guido van Rossum
程式的易讀性是python哲學中非常重要的一部分,程式有好的文件才會有人願意使用。
例如pandas
和scikit-learn
的文件都非常優秀,讓使用者可以找到清楚的說明與範例。
好的文件同時也是為了避免未來的自己或是你的同事,陷入痛苦的困惑當中。
為程式加入註解#
'#'
是註解符號,符號後面的文字會忽略,不執行。
以下範例來自<精通無瑕程式碼>:
import re
text = '''
Ha! let me see her: out, alas! She's cold:
Her blood is settled, and her joints are stiff;
Life and these lips have long been separated:
Death lies on her like an ultimely frost
Upon the sweetest flower of all the field.
'''
f_words = re.findall('\\bf\w+\\b', text)
print(f_words)
l_words = re.findall('\\bl\w+\\b', text)
print(l_words)
['frost', 'flower', 'field']
['let', 'lips', 'long', 'lies', 'like']
以上程式片段對於不熟悉正規表達式的人,會很難以理解程式碼的作用。 但加上一些註解後就可以消除困惑:
import re
text = '''
Ha! let me see her: out, alas! She's cold:
Her blood is settled, and her joints are stiff;
Life and these lips have long been separated:
Death lies on her like an ultimely frost
Upon the sweetest flower of all the field.
'''
# 找出所有'f'開頭的字元
f_words = re.findall('\\bf\w+\\b', text)
print(f_words)
# 找出所有'l'開頭的字元
l_words = re.findall('\\bl\w+\\b', text)
print(l_words)
['frost', 'flower', 'field']
['let', 'lips', 'long', 'lies', 'like']
但是也要避免不必要的註解,以下範例一樣來自<精通無瑕程式碼>:
investments = 10000 # 你的投資金額,必要時可改
yearly_return = 0.1 # 年報酬率
years = 10 # 複利計算的年數
# 逐年計算
for year in range(years):
# 列印今年的投資價值
print(investments * (1 + yearly_return)**year)
10000.0
11000.0
12100.000000000002
13310.000000000004
14641.000000000004
16105.100000000006
17715.610000000008
19487.171000000013
21435.888100000015
23579.47691000002
因為很多東西都是顯而易見的,有意義的變數名稱就已經可以說明許多事情,就不必要再做行內註解。 (行內註解意思是在同一行程式碼的後面直接加上註解)
而且該知道的常識也沒必要註解,例如大家都知道for迴圈就是為了逐個計算,就不必註解for迴圈的功能。
Docstring#
docstring
指的是function的def
敘述句之後的說明片段。範例如下:
def my_func(*args, **kwargs):
'''convert probability to log odds (logit).
'''
print(f'Positional args: {args}')
print(f'Keyword args: {kwargs}')
有了docstring後,就可以使用以下方式來讀取你撰寫的說明:
print(help(my_func))
Help on function my_func in module __main__:
my_func(*args, **kwargs)
convert probability to log odds (logit).
None
或是:
print(my_func.__doc__)
convert probability to log odds (logit).