Python函式

以下為此單元簡報。

https://drive.google.com/file/d/12wze72ihfGobm4iRso2gP3oHfcFoTlt6/view?usp=sharing

以下為本單元的教學影片(放置於YouTube上),以下兩種方式擇一。

(A)以YouTube播放清單方式觀看影片,如下。

https://www.youtube.com/playlist?list=PLOk9WKoJdjHbX7r46JJjGCuqaYBahJpjP

(B)以超連結方式觀看個別影片,如下。

Python-6-1-函式

Python-6-2-函式與變數作用範圍

Python-6-3-函式範例

Python-6-4-函式的輸入

Python-6-5-函式的回傳值

函式

函式用於結構化程式,將相同功能的程式獨立出來,經由函式的呼叫,傳入資料與回傳處理後的結果,程式設計師只要將函式寫好,可以不斷利用此函式做相同動作,可以達成程式碼不重複,要修改此功能,只要更改此函式。

(A-1) 函式的定義、傳回值與呼叫

自訂函式需要包含兩個部分,分別是「函式的定義」與「函式的呼叫」。「函式的定義」是實作函式的功能,輸入參數與回傳處理後的結果,「函式的呼叫」是其他程式中呼叫自訂函式,讓自訂函式真正執行,以下分開敘述函式的定義與呼叫。

函式的定義

以def開頭,空一個空白字元(space),接函式名稱後,串接著一對小括號,小括號可以填入要傳入函式的參數,當參數有多個的時候以逗號隔開,右小括號後面須接上「:」,函式範圍為縮行固定個數空白字元的程式碼,縮行相同空白字元的程式碼就是函式的作用範圍。當函式需要傳回值使用指令return,表示函式回傳資料給原呼叫函式,若不需要回傳值的函式就不需要加上return,函式的定義與傳回值格式,如下表。

函式的呼叫

程式經由函式呼叫,將資料傳入函式,函式處理後傳回結果給呼叫程式,程式中如何呼叫函式?在程式中利用函式名稱與參數來呼叫函式。

方法一:無傳回值的呼叫語法

函式名稱(參數值1,參數值2,…)

方法二:有傳回值的呼叫語法

變數=函式名稱(參數值1,參數值2,…)

等號右邊要先做完,利用函式名稱與參數來呼叫函式,最後函式回傳值給變數,變數就紀錄函式呼叫後的回傳值。

綜合前面敘述,函式定義與函式呼叫範例,如下表。

1

2

3

4

5

6

7

8

9

範例

def hi():

print('hi')

hi()

def min(a,b):

if a > b:

return b

else:

return a

print(min(2,4))

執行結果

hi

2

解說

第1到2行:自訂函式hi,印出「hi」到螢幕上。

第3行:呼叫hi函式。

第4到8行:自訂函式min,輸入兩個參數a與b,將較小的數字回傳回來。

第9行:呼叫函式min,輸入數字2與4,回傳較小的數字2到螢幕上。

以下範例用於計算長方形面積,使用者輸入長度與寬度,呼叫自訂的函式area將長度與寬度傳入,回傳計算結果。

1

2

3

4

5

6

函式範例程式

def area(x,y):

return x*y

a = int(input('請輸入長度?'))

b = int(input('請輸入寬度?'))

ans = area(a, b)

print('長方形面積為', ans)

執行結果

請輸入長度?3

請輸入寬度?4

長方形面積為 12

解說

第1行到第2行:函式area的定義,輸入參數x與y,回傳x乘以y的結果。

第3行:於螢幕輸出「請輸入長度?」,經由函式input輸入字串,再由函式int將字串轉換成整數指定給變數a。

第4行:於螢幕輸出「請輸入寬度?」,經由函式input輸入字串,再由函式int將字串轉換成整數指定給變數b。

第5行:呼叫area函式,使用a與b為參數,將函式回傳值儲存入變數ans。

第6行:使用函式print顯示字串「長方形面積為」串接變數ans在螢幕上。

6-1-2. 函式與變數的作用範圍

變數作用範圍分成全域變數與函式內的區域變數,宣告在最上面最外層的稱作全域變數,宣告在函式內的變數稱作區域變數,函式內若沒有那個變數就會往函式外找尋,舉例如以下範例。

1

2

3

4

範例

g = 5

def f1():

print(g)

f1()

執行結果

5

解說

第3行的print(g),因為函式內沒有變數g,所以往函式外去找尋,找到第1行的變數g,將全域變數g的數值5顯示在螢幕上。

若函式內有一個區域變數,與全域變數名稱一樣的變數,若讀取區域變數在初始化區域變數之前,則會產生UnboundLocalError錯誤,下表中程式第3行,因為會產生UnboundLocalError錯誤,所以使用井字號「#」進行註解讓該行沒有作用,若要測試此錯誤就可以將井字號「#」刪除,再執行程式一次,就會出現UnboundLocalError錯誤。從以下程式可以發現,全域變數g與區域變數g,是兩個不同的變數,函式內區域變數g作用範圍在函式內,全域變數g作用範圍為整個檔案,但因為函式內區域變數有相同的變數名稱,函式會優先使用區域變數,若找不到才去找全域變數。

1

2

3

4

5

6

7

範例

g = 5

def f2():

#print(g)

g = 10

print(g)

f2()

print(g)

執行結果

10

5

解說

第1行:宣告全域變數g,初始化為5。

第2到5行:定義函式f2,宣告區域變數g,並初始化為10(第4行),印出區域變數g的值到螢幕上(第5行)。

第6行:呼叫函式f2。

第7行:印出全域變數g的值到螢幕上。

函式內若沒有那個變數就會往函式外去找尋,也可以使用global宣告區域變數,該區域變數將明確指向全域變數,也就是宣告為global的區域變數一定指向相同名稱的全域變數,舉例如以下範例。

1

2

3

4

5

6

7

8

範例

g = 5

def f():

global g

print(g)

g = 10

print(g)

f()

print(g)

執行結果

5

10

10

說明

第1行:宣告全域變數g,初始化為5。

第2到6行:定義函式f,宣告g為全域變數(第3行),印出全域變數g的值到螢幕上(第4行),設定全域變數g為10(第5行) ,印出全域變數g的值到螢幕上(第6行)。

第7行:呼叫函式f。

第8行:印出全域變數g的值到螢幕上。

(B) 函式範例練習

(B-1) 計算BMI

BMI常用來判斷肥胖程度,BMI等於體重(KG)除以身高(M)的平方,「BMI與肥胖等級標準」表,如下。請寫一個程式讓使用者輸入體重與身高,顯示BMI值與肥胖程度。

(a)解題想法

我們利用自訂函式BMI,輸入體重與身高回傳BMI值,利用BMI值與「BMI與肥胖等級標準」表,使用選擇結構顯示BMI值與對應的肥胖等級。

(b)程式碼新與解說

1

2

3

4

5

6

7

8

9

10

11

12

13

14

程式碼

def BMI(w, h):

return w/(h*h)

w = float(input('請輸入體重(KG)?'))

h = float(input('請輸入身高(M)?'))

bmi = BMI(w, h)

print('BMI為', bmi)

if (bmi < 18):

print('體重過輕')

elif (bmi < 24):

print('體重正常')

elif (bmi < 27):

print('體重過重')

else:

print('體重肥胖')

執行結果

請輸入體重為「80」與身高為「1.7」,顯示計算所得BMI值為「27.68166089965398」與肥胖分級為「體重肥胖」。

請輸入體重(KG)?80

請輸入身高(M)?1.7

BMI為 27.68166089965398

體重肥胖

解說

第1到2行:定義BMI函式,使用體重與身高為輸入值,回傳BMI值。體重儲存於變數w,身高儲存於變數h,回傳「w/(h*h)」(第2行)。

第3行:於螢幕輸出「請輸入體重(KG)?」,經由函式input輸入字串,再由函式float將字串轉換成浮點數,指定給變數w。

第4行:於螢幕輸出「請輸入身高(M)?」,經由函式input輸入字串,再由函式float將字串轉換成浮點數,指定給變數h。

第5行:呼叫BMI函式,使用w與h為參數,將所得BMI值指定給變數bmi。

第6行:將函式所得的BMI值顯示在螢幕。

第7到8行:判斷所計算出的BMI值是否小於18,若是則顯示「體重過輕」。

第9到10行:否則判斷所計算出的BMI值是否小於24(隱含成績大於等於18),若是則顯示「體重正常」。

第11到12行:否則判斷所計算出的BMI值是否小於27(隱含成績大於等於24),若是則顯示「體重過重」。

第13到14行:否則(隱含成績大於等於27)顯示「體重肥胖」。

(C) 函式的輸入與輸出

(C-1)函式的輸入

函式中有預設值的輸入參數一定要放在後面,預設值要是不可以變的常數,不能為串列或字典等可以修改的資料結構。

1

2

3

範例

def f(s, count=1):

print(s * count)

f('Hi')

執行結果

Hi

解說

第1到2行:定義函式f,輸入參數有s與count,參數count預設值為1,使用print印出count個字串s到螢幕(第2行)。

第3行:呼叫函式f,設定s為「Hi」,變數count沒有輸入值,使用預設值1。

可以經由函式呼叫輸入新的數值取代原預設值,如以下範例。

1

2

3

範例

def f(s, count=1):

print(s * count)

f('Hi',3)

執行結果

HiHiHi

解說

第1到2行:定義函式f,輸入參數有s與count,參數count預設值為1,使用print印出count個字串s到螢幕(第2行)。

第3行:呼叫函式f,設定s為「Hi」,設定count為3,取代預設值1。

函式內參數的對應,若未指定名稱會依照順序填入,例如:自訂函式func如下。

def func(x, y, z=9):

print("x=" , x , "y=" , y , "z=" , z)

若以func(1,2)進行呼叫,會印出「x= 1 y= 2 z= 9」,可以看出依照順序放入對應的參數;若以func(1, 2, 3)進行呼叫,會印出「x= 1 y= 2 z= 3」,可以看出依照順序放入對應的參數,且預設值被輸入值取代。若以func(x=3, y=4)進行呼叫,會印出「x= 3 y= 4 z= 9」,可以指定參數與輸入值的對應,這時就可以不用依照順序,例如以func(y=5, x=6)進行呼叫,會印出「x= 6 y= 5 z= 9」,所以指定參數與輸入值的對應,輸入順序與參數的對應順序可以不同。以func(x=3, z=6)進行呼叫,會發生TypeError,因為參數y沒有輸入值,沒有預設值的參數一定需要輸入值。

1

2

3

4

5

6

7

範例

def func(x, y, z=9):

print("x=" , x , "y=" , y , "z=" , z)

func(1, 2)

func(1, 2, 3)

func(x=3, y=4)

func(y=5, x=6)

#func(x=3, z=6)

執行結果

x= 1 y= 2 z= 9

x= 1 y= 2 z= 3

x= 3 y= 4 z= 9

x= 6 y= 5 z= 9

解說

第1到2行:定義函式func,輸入參數有x、y與z,參數z預設值為9,使用print印出參數x、y與z的值到螢幕(第2行)。

第3行:呼叫函式func,依序設定參數x為1,參數y為2,參數z使用預設值。

第4行:呼叫函式func,依序設定參數x為1,參數y為2,參數z為3。

第5行:呼叫函式func,使用指定方式設定參數x為3,參數y為4,參數z使用預設值。

第6行:呼叫函式func,使用指定方式設定參數y為5,參數x為6,參數z使用預設值。

第7行:呼叫函式func,使用指定方式設定參數x為3,參數z為6,但沒有指定參數y,所以會產生TypeError錯誤,此行先以井字號「#」開頭當成註解,若要產生TypeError錯誤,請將開頭的井字號「#」去除,重新執行就會產生TypeError錯誤。

(C-2)函式的回傳值

函式回傳值可以使用tuple回傳多個資料,例如:以下ymd函式使用tuple回傳時間的年、月與日。

def ymd():

now = datetime.now()

return (now.year, now.month, now.day)

回傳的tuple可以使用tuple開箱(tuple uppacking)取得回傳的多個參數,如下。

y, m, d = ymd()

y會對應到時間的年,m會對應到時間的月,而d會對應到時間的日,到此就完成回傳多個資料的功能。

1

2

3

4

5

6

範例

from datetime import datetime

def ymd():

now = datetime.now()

return (now.year, now.month, now.day)

y, m, d = ymd()

print(y,m,d)

執行結果

2016 5 23

解說

第1行:匯入函式庫datetime的模組datetime。

第2到4行:定義函式ymd,設定變數now為模組datetime的函式now回傳的物件,使用return回傳tuple,tuple內元素依序為目前時間的年(now.year)、目前時間的月(now.month)與目前時間的日(now.day)。

第5行:呼叫函式ymd,將回傳的tuple,依序放入變數y、m與d。

第6行:使用函式print依序顯示變數y、m與d的值到螢幕上。

以下為進階函式概念,初學者可以暫時跳過。

(C-3) 函式的進階輸入 — 位置引數與關鍵字引數

(A)位置引數(positional arguments)

位置引數(函式的輸入變數前使用*)會將函數內多個輸入值群組化成tuple,例如以下範例,慣例使用args為位置引數名稱,這個變數名稱可以修改成任何變數名稱。

def func1(*args):

print('位置引數為', args)

使用「func1(1,2,3)」呼叫函式func1,會印出以下結果。

位置引數為 (1, 2, 3)

可以發現args為tuple,內容為「(1, 2, 3)」。

(B)關鍵字引數(keyword arguments)

關鍵字引數(函式的輸入變數前使用**)會將函數內多個輸入值群組化成字典,例如以下範例,慣例使用kwargs為關鍵字引數名稱,這個變數名稱可以修改成任何變數名稱。

def func2(**kwargs):

print('關鍵字引數為', kwargs)

使用「func2(a=1, b=2)」呼叫函式func2,會印出以下結果。

關鍵字引數為 {'b': 2, 'a': 1}

可以發現kwargs為字典,內容為「{'b': 2, 'a': 1}」。

(C)參數、位置引數與關鍵字引數

參數、位置引數與關鍵字引數可以一起使用,如以下範例。

def func3(start, *args, **kwargs):

print("start=", start)print("位置引數為", args)

print("關鍵字引數為", kwargs)

使用「func3(1, 2, 3, a=4, b=5)」呼叫函式func3,會印出以下結果。

start= 1

位置引數為 (2, 3)

關鍵字引數為 {'b': 5, 'a': 4}

可以發現第一個數字1指定給start,args為tuple,內容為「(2, 3)」,kwargs為字典,內容為「{'b': 5, 'a': 4}」。

綜合上述範例獲得以下程式。

1

2

3

4

5

6

7

8

9

10

11

範例

def func1(*args):

print('位置引數為', args)

func1(1,2,3)

def func2(**kwargs):

print('關鍵字引數為', kwargs)

func2(a=1, b=2)

def func3(start, *args, **kwargs):

print("start=", start)

print("位置引數為", args)

print("關鍵字引數為", kwargs)

func3(1, 2, 3, a=4, b=5)

執行結果

位置引數為 (1, 2, 3)

關鍵字引數為 {'b': 2, 'a': 1}

start= 1

位置引數為 (2, 3)

關鍵字引數為 {'b': 5, 'a': 4}

解說

第1到2行:定義函式func1,允許將所有輸入值轉成位置引數,顯示位置引數在螢幕上(第2行)。

第3行:呼叫函式func1,使用「1,2,3」為輸入。

第4到5行:定義函式func2,允許將所有輸入值轉換成關鍵字引數,顯示關鍵字引數在螢幕上(第5行)。

第6行:呼叫函式func2,使用「a=1, b=2」為輸入。

第7到10行:定義函式func2,允許將所有函式輸入值轉換成參數、位置引數與關鍵字引數,顯示參數(第8行)、位置引數(第9行)與關鍵字引數(第10行)在螢幕上。

第11行:呼叫函式func3,使用「1, 2, 3, a=4, b=5」為輸入。

(D) 函式的說明文件

可以在函式下方使用「'''」撰寫函式的說明文件,說明文件可以跨好幾行,直到找到下一個「'''」,使用「'''」會保留第2行以後所有開頭的空格,如以下範例。

def min(a, b):

''' 使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

'''

if a > b:

return b

else:

return a

使用「help(min)」可以讀取函式的說明文件,如下。

Help on function min in module __main__:

min(a, b)

使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

使用「print(min.__doc__)」可以讀取函式的說明文件,如下。

使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

我們可以善用函式的說明文件,讓後續維護程式的程式設計師可以快速了解函式的用途與功能。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

範例

def min(a, b):

''' 使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

'''

if a > b:

return b

else:

return a

help(min)

print(min.__doc__)

執行結果

Help on function min in module __main__:

min(a, b)

使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

使用min可以找出a與b較小的值

Args:

a: 輸入的第一個參數

b: 輸入的第二個參數

Returns:

回傳a與b中較小的值

解說

第1到13行:定義函式min,函式說明在第2到9行。若a大於b,則回傳b,否則回傳a(第10到13行)。

第14行:使用函式help,讀取函式min的說明文件。

第15行:使用函式print印出函式min的說明文件,函式的說明文件屬性為(__doc__)。

(E) 函式視為物件

Python中函式視為物件,以函式名稱當成物件,函式名稱加上()才會執行該函式,範例如下。

def add(a, b):

return a + b

def run(func, x, y):

return func(x, y)

k = run(add, 10, 20)

print('k=', k)

函式run的第1個參數func為函式物件,使用func()呼叫執行func所指定的函式,run函式以「run(add, 10, 20)」執行,則func會使用函式物件add取代,run函式中的func(x,y)相當於add(x,y),可以看出Python把函式名稱當成物件使用,上述程式執行結果如下。

k= 30

1

2

3

4

5

6

範例

def add(a, b):

return a + b

def run(func, x, y):

return func(x, y)

k = run(add, 10, 20)

print('k=', k)

執行結果

k= 30

解說

第1到2行:定義函式add,輸入兩個參數a與b,回傳a加上b的結果。

第3到4行:定義函式run,輸入三個參數func、x與y,func需傳入函式物件,函式func傳入兩個參數x與y,回傳函式func的結果。

第5行:使用「run(add, 10, 20)」相當於執行add(10, 20),回傳結果指定給變數k。

第6行:使用函式print顯示「k=」與變數k的值到螢幕上。

(F) 函式lambda

函式若只有一行,可以轉換成為函式lambda,函式lambda的轉換格式如下。

lambda 輸入的參數:函式的定義

我們可以將函式add轉換成函式lambda,如下。

原始函式

def add(a, b):

return a + b

轉換為lambda

lambda a, b: a+b

使用函式lambda重新改寫(E)的範例程式,如下。

def run(func, x, y):

return func(x, y)

k = run(lambda a,b: a+b, 10, 20)

print(k)

函式run的第1個參數func為函式物件,使用func()呼叫執行func所指定的函式,run函式以「run(lambda a,b: a+b, 10, 20)」執行,則func會使用函式lambda所定義的函式取代,函式run內的func(x,y)相當於函式lambda以x與y為輸入參數,上述程式執行結果如下。

k= 30

行號

1

2

3

4

範例

def run(func, x, y):

return func(x, y)

k = run(lambda a,b: a+b, 10, 20)

print('k=', k)

執行結果

k= 30

解說

第1到2行:定義函式run,輸入三個參數func、x與y,func為傳入的函式物件,函式func傳入兩個參數x與y,回傳執行函式func的結果。

第3行:使用「run(lambda a,b: a+b, 10, 20)」相當於執行「10+20」,回傳結果儲存到變數k。

第4行:使用函式print顯示「k=」與變數k的值到螢幕上。

(G) 產生器(generator)

使用函式製作產生器,產生器可以產生一個序列的資料,產生器要使用yeild回傳資料,而非使用return回傳資料,使用yeild回傳資料會紀錄上一次回傳時函式的狀態,不會從頭到尾都執行。使用產生器的好處是不用一次產生所有資料,當產生的資料量很大時會占用很多記憶體空間,產生器會一次產生一個資料,紀錄上一次執行的狀態,需要時再產生下一個資料。還記得迴圈時使用的函式range,我們使用產生器撰寫自己的函式range,取名叫函式irange,產生器程式如下。

def irange(start, stop, step=1):

if start < stop:

i = start

while i < stop:

yield i

i = i + step

else:

i = start

while i > stop:

yield i

i = i + step

使用yield回傳資料的函式會被認為是產生器,執行以下程式。

x = irange(1,10)

print(x)

印出以下結果。

<generator object irange at 0x00000000010FA360>

表示x是一個產生器(generator)

以下程式使用for迴圈取出產生器所產生的序列元素。

for i in irange(1, 5, 1):

print(i)

印出以下結果。

1

2

3

4

行號

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

範例

def irange(start, stop, step=1):

if start < stop:

i = start

while i < stop:

yield i

i = i + step

else:

i = start

while i > stop:

yield i

i = i + step

x = irange(1,10)

print(x)

for i in irange(1, 5, 1):

print(i)

for i in irange(4, 1, -1):

print(i)

程式執行結果

<generator object irange at 0x000000000110A360>

1

2

3

4

4

3

2

解說

第1到11行:定義函式irange,輸入三個參數start、stop與step,step沒有輸入值時,使用1為預設值。若start小於stop(第2行),表示為遞增數列,則i設定為start(第3行),當i小於stop,則使用yield回傳變數i的值,接著i值遞增step(第4到6行);否則為遞減數列,i設定為start(第8行),當i大於stop,則使用yield回傳變數i的值,接著i值遞增step(第9到11行)。

第12行:設定x為irange(1,10)。

第13行:印出x到螢幕上。

第14到15行:使用for迴圈取出產生器irange(1, 5, 1)的每個元素到i,使用函式print印出i的值到螢幕。

第16到17行:使用for迴圈取出產生器irange(4, 1, -1)的每個元素到i,使用函式print印出i的值到螢幕。

(H) 內部函式

Python函式內部可以包含另一個函式,函式內部的函式稱作內部函式。內部函式用於函式內會一直重複利用到的功能可以獨立出來寫成一個函式,在函式內呼叫使用,例如以下範例。

def hello(msg):

def say(text):

return 'Hello,'+text

print(say(msg))

print(say('你好'))

函式hello內定義函式say,在函式hello內呼叫了兩次say,分別傳了一個字串回來,使用函式print將字串列印出來,執行時使用以下程式呼叫函式hello。

程式執行結果如下。

Hello,John

Hello,你好

hello('John')

行號

1

2

3

4

5

6

範例

def hello(msg):

def say(text):

return 'Hello,'+text

print(say(msg))

print(say('你好'))

hello('John')

執行結果

Hello,John

Hello,你好

解說

第1到5行:定義函式hello,輸入參數msg,在函式內定義內部函式say,輸入參數text,內部函式say用於回傳「Hello,」串接參數text的字串(第2到3行)。呼叫內部函式say以msg為輸入,將結果印出在螢幕上,再一次呼叫內部函式say以「你好」為輸入,將結果印出在螢幕上。

第6行:呼叫函式hello以「John」為輸入。

(I) closure函式

類似前一節的內部函式,在函式內動態建立一個函式,回傳該函式,稱作closure。使用closure的好處是可以看到外部函式的變數,讓程式碼集中在外部函式內,而非宣告定義在外部函式之外,避免程式碼分散不易閱讀。將前一節程式改成closure,程式碼如下。

def hello(msg):

def say(hi):

return hi+msg

return say

在外部函式hello內動態建立一個函式say,函式say會回傳參數hi(函式say的參數)串接參數msg(函式hello的參數)的字串,最後將函式say當成物件回傳,內部函式say稱作closure函式,使用closure函式的好處是內部函式say(closure函式)能夠存取外部函式hello的參數msg。呼叫函式hello回傳函式物件say到變數,例如以下程式碼,變數x與y都是函式物件。

使用「x()」與「y()」執行closure函式回傳字串,再利用函式print列印出來。

x=hello('Claire')

y=hello('Fiona')

print(x('Hello,'))

print(y('Hi,'))

執行結果如下。

Hello,Claire

Hi,Fiona

1

2

3

4

5

6

7

8

範例

def hello(msg):

def say(hi):

return hi+msg

return say

x=hello('Claire')

y=hello('Fiona')

print(x('Hello,'))

print(y('Hi,'))

程式執行結果

Hello,Claire

Hi,Fiona

解說

第1到4行:定義函式hello,輸入參數msg,在函式內定義內部函式say,輸入參數hi,內部函式say用於回傳參數hi(函式say的參數)串接參數msg(函式hello的參數)的字串(第2到3行),最後回傳函式物件say。

第5行:呼叫函式hello以「Claire」為輸入,將回傳的函式物件指定給變數x。

第6行:呼叫函式hello以「Fiona」為輸入,將回傳的函式物件指定給變數y。

第7行:呼叫函式x以「Hello,」為輸入,將回傳的字串利用函式print顯示在螢幕上。

第8行:呼叫函式x以「Hi,」為輸入,將回傳的字串利用函式print顯示在螢幕上。

(J) Decorator(裝飾器)

裝飾器為一種函式,允許輸入一個函式,回傳另一個函式,常用於將一個自訂函式改裝成另一個函式,可以用於顯示除錯訊息,例如定義一個除錯函式如下。

def debug(func1):

def func2(*args, **kwargs):

print('正在執行函式', func1.__name__)

print('函式的說明文件為', func1.__doc__)

print('位置引數', args)

print('關鍵引數', kwargs)

return func1(*args, **kwargs)

return func2

函式debug允許輸入一個函式func1,在函式debug內定義另一個函式func2,函式func2接收傳入函式func2的位置引數args與關鍵字引數kwargs,顯示函式func1的函式名稱、說明文件到螢幕上,接著顯示傳入函式func2的位置引數與關鍵字引數在螢幕上,回傳函式func1以位置引數args與關鍵字引數kwargs為輸入的結果,最後函式debug執行結束回傳函式func2。

使用上可以輸入一個函式物件到裝飾器,使用變數接收裝飾器所回傳的函式物件,輸入到裝飾器的函式名稱與接收裝飾器所回傳的函式名稱可以使用相同函式名稱,如以下範例都使用add,但輸入的函式物件add與回傳的函式物件add是指向不同的函式物件,也就是經由裝飾器可以將函式物件轉換成另一個函式物件。

def add(a, b):

'回傳a加b的結果'

return a+b

add = debug(add)

print(add(1, b=2))

上述程式執行結果如下。

正在執行函式 add

函式的說明文件為 回傳a加b的結果

位置引數 (1,)

關鍵引數 {'b': 2}

3

也可以利用「@」簡化裝飾器的使用步驟,在需要裝飾器的函式的上一行,使用「@」串接裝飾器名稱,就可以達成使用裝飾器改變下一行所定義的函式。

@debug

def add(a, b, c):

'回傳a+b+c的結果'

return a+b+c

print(add(1, 2, c=3))

上述程式執行結果如下。

正在執行函式 add

函式的說明文件為 回傳a+b+c的結果

位置引數 (1, 2)

關鍵引數 {'c': 3}

6

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

範例

def debug(func1):

def func2(*args, **kwargs):

print('正在執行函式', func1.__name__)

print('函式的說明文件為', func1.__doc__)

print('位置引數', args)

print('關鍵引數', kwargs)

return func1(*args, **kwargs)

return func2

def add(a, b):

'回傳a加b的結果'

return a+b

add = debug(add)

print(add(1, b=2))

@debug

def add(a, b, c):

'回傳a+b+c的結果'

return a+b+c

print(add(1, 2, c=3))

執行結果

正在執行函式 add

函式的說明文件為 回傳a加b的結果

位置引數 (1,)

關鍵引數 {'b': 2}

3

正在執行函式 add

函式的說明文件為 回傳a+b+c的結果

位置引數 (1, 2)

關鍵引數 {'c': 3}

6

解說

第1到8行:函式debug允許輸入一個函式func1,在函式debug內定義另一個函式func2,函式func2接收傳入函式func2的位置引數args與關鍵字引數kwargs,顯示函式func1的函式名稱、說明文件到螢幕上(第3到4行),接著顯示傳入函式func2的位置引數與關鍵字引數在螢幕上(第5到6行),回傳函式func1以位置引數args與關鍵字引數kwargs為輸入的結果(第7行),最後函式debug執行結束回傳函式func2(第8行)。

第9到11行:定義函式add,輸入兩個參數a與b,增加函式的說明,回傳a加b相加的結果。

第12行:呼叫函式debug,輸入函式物件add,回傳的函式物件指定給函式物件add。

第13行:函式物件add,以「1,b=2」為輸入,使用函式print將結果顯示在螢幕上。

第14行:使用裝飾器debug。

第15到17行:定義函式add,輸入三個參數a、b與c,增加函式的說明,回傳a加b加c的結果。

第18行:函式物件add,以「1,2,c=3」為輸入,使用函式print將結果顯示在螢幕上。