0%

很特別的可以直接當作一個函式的參數來使用。如果當作函式的參數,可以用來限定*之後不可以使用位置引數,但可以使用關鍵字引數,這樣的使用方法被稱為Keyword-Only Arguments

以下以位置引數(Positional Argument)與關鍵字引數(Keyword Argument)中的計算小考平均成績為例說明:

1
2
def total_score(eng_score, math_score, eng_weight=0.5, math_weight=0.5):    
return eng_scoreeng_weight + math_scoremath_weight

在這個範例中,可以使用以下4種方式來呼叫函式都是沒問題的

1
2
3
4
5
6
7
8
>>> total_score(80, 90)  # 省略且不指定有預設值的參數
85
>>> total_score(eng_score=80, eng_weight=0.2, math_score=90, math_weight=0.8) # 使用關鍵字引數,可隨意安排順序
88
>>> total_score(80, 90, math_weight=0.8, eng_weight=0.2) # 混用位置和關鍵字引數
88
>>> total_score(80, 90, 0.2, 0.8) # 單純使用位置引數
88

但如果今天在原本的函式中,把*加入到第三個參數,這樣在第四種單純使用位置引數來作函式呼叫就會引發TypeError,因為在之後不能使用位置引數

1
2
3
4
5
def total_score(eng_score, math_score, * , eng_weight=0.5, math_weight=0.5):    
return eng_scoreeng_weight + math_score*math_weight

>>> total_score(80, 90, 0.2, 0.8) # 單純使用位置引數
TypeError: total_score() takes 2 positional arguments but 4 were given

今天如果要設計成防止呼叫者使用任何位置引數,就可以將*做為第一個參數,這樣就可以強迫叫者全部使用關鍵字引數,若使用任何的位置引數,都會引發TypeError

1
2
3
4
5
6
7
8
9
10
11
def total_score(*, eng_score, math_score, eng_weight=0.5, math_weight=0.5):    
return eng_scoreeng_weight + math_score*math_weight

>>> total_score(80, 90) # 省略且不指定有預設值的參數
TypeError: total_score() takes 0 positional arguments but 2 were given

>>> total_score(80, 90, math_weight=0.8, eng_weight=0.2) # 混用位置和關鍵字引數
TypeError: total_score() takes 0 positional arguments but 2 positional arguments (and 2 keyword-only arguments) were given

>>> total_score(80, 90, 0.2, 0.8) # 單純使用位置引數
TypeError: total_score() takes 0 positional arguments but 4 were given

PEP 3102 – Keyword-Only Arguments

我們可以透過拆開運算符,來達到建立具有接收不固定位置引數和不估定關鍵字引數的函式。假設我們今天要建立一個,可以讀取不固定引數數量的函式,就會很有用。

舉例來說,當我們要建立一個能將所有引數平方相加函式,可以使用下列寫法:

1
2
3
4
5
6
7
8
9
10
11
12
def sum_of_power(args):
result = 0
for arg in args:
result += arg ** 2
return result

>>> sum_of_power(1, 2, 3, 4)
30
>>> sum_of_power([2, 2, 2])
12
>>> sum_of_power(*[3, 4, 5, 6][2:]))
61

有*前綴的argv表示函式接收的引數是一個元組(type(args)會得到<class ‘tuple’=””>),像是把傳入的多個引數packing成一個元組,再透過拆開運算符來將元組拆開成多個位置引數作處理。而關鍵字引數也可以應用在位置引數之後,假如我們要讓相加的引數改成可指定的次方向,可在函式加上關鍵字引數:

1
2
3
4
5
6
7
8
9
10
def sum_of_power(args power=2):
result = 0
for arg inargs:
result += arg * power
return result

>>> sum_of_power(1, 2, 3, 4) # 僅指定位置引數
30
>>> sum_of_power(1, 2, 3, 4, power=1) # 同時指定位置引數和關鍵字引數
10

有兩個*前綴的kwargv表示接收的引數是一個字典(type(kwargs)會得到<class ‘dict’=””>),像是把傳入的多個關鍵字引數packing成一個字典,再透過拆開運算符來將字典拆開成多個關鍵字引數作處理。假設今天要針對不同會員設定多個會員詳細資料,可以用下列寫法:

1
2
3
4
5
6
7
8
9
def personal_detail(account, **kwargs):    
print('Account detail: ' + account)
for key in kwargs.keys():
print(key + ': ' + kwargs[key])

>>> personal_detail('a1234@gmail.com', first_name='Tom', last_name='Lin')
Account detail: a1234@gmail.com
first_name: Tom
last_name: Lin

呼叫此函式時,只允許傳入一個位置引數,或是可以用關鍵字引數方式,來額外提供多個資訊。

參考資料:
位置引數(Positional Argument)與關鍵字引數(Keyword Argument)
透過*和**來對群集資料Unpacking
Python docs - Arbitrary Argument Lists

Python函式參數所接收的引數(Argument),主要可以分成兩種,分別為位置引數(Positional Argument)和關鍵字引數(Keyword Argment),以下我們先來看個例子,說明什麼是位置引數

1
2
3
4
5
def total_score(eng_score, math_score, eng_weight, math_weight):    
return eng_scoreeng_weight + math_scoremath_weight

>>> total_score(80, 90, 0.5, 0.5)
85

假設今天我們寫了一個簡單的函式來計算小考的平均成績,共有英文和數學兩科,兩科各有一個權重比例來計算出綜合成績。這個函式要傳入4個引數,所以在呼叫時我們必須提供所有的引數(即4個),而傳入的引數會被設成相應位置上參數的值。

eng_score被設成80、eng_weight被設成0.2、math_score被設成90、math_weight被設成0.8,這就是位置引數

函式接收的參數,也可以指定一個預設值。假設預設的計算分數的權重都是固定一半一半,我們可以在函式撰寫時就給予一個預設的權重值。有預設值的參數為選用參數,在沒有傳入引數的情況下,python會使用預設值;而沒有預計值的參數為必要參數,在呼叫函式時必須傳入所有必要參數

1
2
def total_score(eng_score, math_score, eng_weight=0.5, math_weight=0.5):    
return eng_scoreeng_weight + math_scoremath_weight

若要改變參數的預設值,我們也可以直接使用額外的位置引數,就可以取代原來的預設值,或者也可以使用關鍵字引數來傳遞引數。以下為幾個呼叫的範例:

1
2
3
4
5
6
7
8
>>> total_score(80, 90)  # 省略且不指定有預設值的參數
85
>>> total_score(eng_score=80, eng_weight=0.2, math_score=90, math_weight=0.8) # 使用關鍵字引數,可隨意安排順序
88
>>> total_score(80, 90, math_weight=0.8, eng_weight=0.2) # 混用位置和關鍵字引數
88
>>> total_score(80, 90, 0.2, 0.8) # 單純使用位置引數
88

其中可以看到使用關鍵字引數的好處在於,關鍵字引數可以讓函式呼叫變得具彈性,即針對自己所需的選用參數來指定引數;甚至也可以讓函式呼叫更加具可讀性,特別在傳入布林引數時。例如在排序時決定要不要作逆向排序,sorted(text, reverse=True)比起sorted(text, True)可讀性來的更高

有兩個要注意的地方,第一個是函式語法不允許具預設值的參數之後接非預設值的參數,會引發SyntaxError。例如:

1
2
def total_score(eng_score, eng_weight=0.5, math_score, math_weight=0.5):
>>> SyntaxError

再來是傳入函式的引數,若是位置引數和關鍵字引數混用的話,位置引數務必放在關鍵字引數的前面,否則也是會引發SyntaxError。所以total_score(math_weight=0.8, eng_weight=0.2, 80, 90)是不行的

在處理python的序列資料(tuple, list)或是映射資料(dict),並要把資料傳入function時,可使用*和**運算符來分別對兩種資料作unpacking並傳入函式

針對序列(tuple, list)可以使用拆開序列運算符(sequence unpacking operator),在變數前加上*前綴來達成

1
2
3
4
5
6
7
8
9
10
11
12
def product(a, b, c):
return a * b * c

T = (2, 5, 10)
product(T[0], T[1], T[2]) # 用切片讀出tuple資料傳入函式
product(*T) # 直接將tuple unpacking成3個資料項傳入函式
product(T[0], *T[1:]) # 先切出1個資料項,再拆開2個資料項

L = [2, 5, 10]
product(L[0], L[1], L[2]) # 用切片讀出list資料傳入函式
product(*L) # 直接將list unpacking成3個資料項傳入函式
product(L[0], *L[1:]) # 先切出1個資料項,再拆開2個資料項

上面的範列product函式會接受3個引數,透過*來直接對序列作unpacking,就能將序列內的資料項直接傳入函式,而不用先讀出來或是對序列作切片(slice)

針對映射資料(dict)則是使用拆開映射運算符(mapping unpacking operator),在變數前加上**前綴

1
2
D = {'a': 2, 'b': 5, 'c': 10}
product(**D)

上面範例會將dic以key-value拆開,在傳入函式後會將每個key的value分配給與key相同名稱的參數(parameter)。要注意的是,如果字典裡面的key和函式參數對應不起來,則會引發TypeError。

除非函式的參數有設定預設值(Default),例如以下範例函式參數d有預計值20,若字典沒有d,d在函式中就會帶入預設值而不會引發TypeError

1
2
3
4
5
def product(a, b, c, d=20):
return a * b * c * d

D = {'a': 2, 'b': 5, 'c': 10}
product(**D)

參考資料:
函式參數預設值請參考: 位置引數(Positional Argument)與關鍵字引數(Keyword Argument)
Python docs - unpacking argument lists

寫python在做字串格式化的時候,常常會使用format來達成。一般簡單的format用法,會使用大括弧加數字編號配合引數順序做取代

1
2
>>> str = "My name is {0}, and I'm {1} years old".format('Tom', 18)
"My name is Tom, and I'm 18 years old"

在Python3.1開始可以省略數字編號

1
2
>>> str = "My name is {}, and I'm {} years old".format('Tom', 18)
"My name is Tom, and I'm 18 years old"

或是使用欄位名稱來取代對應引數

1
2
str = "My name is {name}, and I'm {age} years old".format(name='Tom', age=18)
"My name is Tom, and I'm 18 years old"

format可以搭配dict一起使用,將dict傳入並取出其中的value來完成取代

1
2
3
>>> d = {'name': 'Tom', 'age': 18}
>>> str = "My name is {0[name]}, and I'm {0[age]} years old".format(d)
"My name is Tom, and I'm 18 years old"

既然可以使用dict來結合format做字串取代,所以我們可透過內建的locals()來取得一個儲存當前區域變數的dict,並把區域變數的值帶入fomat來取代對應引數

1
2
3
4
>>> name = 'Tom'
>>> age = 18
>>> str = "My name is {0[name]}, and I'm {0[age]} years old".format(locals())
"My name is Tom, and I'm 18 years old"

我們可以再進一步使用拆開映射(maping unpacking)運算來將dic拆開來傳入format,python就會自動填入對應的值

1
2
3
4
>>> name = 'Tom'
>>> age = 18
>>> str = "My name is {name}, and I'm {age} years old".format(**locals())
"My name is Tom, and I'm 18 years old"

任何的dict都可以使用,上面的例子可以改寫成

1
2
3
>>> d = {'name': 'Tom', 'age': 18}
>>> str = "My name is {name}, and I'm {age} years old".format(**d)
"My name is Tom, and I'm 18 years old"

參考資料:
Python Unpacking請參考: 透過*和**來對群集資料Unpacking