iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 16
0
Data Technology

30天python雜談系列 第 16

版本差異雜談之六———pos_args與kw_args之亂

  • 分享至 

  • xImage
  •  

python 版本差異雜談之六

囉唆一下:今天鑽牛角尖的程度也不相上下,因為python參數設定的細節也挺多的

差異六:函數的參數設定

2跟3的版本之間還有一個很重要但卻不明顯的差異就在於函數參數的設置,在這裡稍微提一下關於python的函數參數,觀念稍微煩雜了點,如果有錯歡迎有人指正我,python函數參數類型有兩種:位置參數(Positional Arguments)以及關鍵字參數(Keyword Arguments),以下我實作不同的函數定義與呼叫來解釋這兩種參數類型的行為與作用。

先用一個函數呼叫範例來說明何為位置參數與關鍵字參數:

return_value = func(1,2,5,kw1=4,kw2=8)

以上是一個多參數的函式,前面的"1,2,5"三個參數就是位置參數,後面的kw1=4,kw2=8是關鍵字參數。

python函數參數有一個鐵則:位置參數在前,關鍵字參數在後,就像上述的呼叫範例一樣,這在python2和python3都是一樣的,因為位置參數是和位置相關的,關鍵字參數則與位置無關,而是與其綁定的名字有關(比如說前面的func(1,2,5,kw1=4,kw2=8)與func(1,2,5,kw2=8,kw1=4)是相同的意思),如果關鍵字參雜在位置參數中,不就把位置參數都搞亂了?

好,大概窺見了這兩個參數類型的樣子了,接著開始詳細說明這兩種參數類型的行為:

example.py:

def func1(x,y,z):
    print(x,y,z)

def func2(x,y,z=5):
    print(x,y,z)

def func3(x,y=3,*pos_args):
    print(x,y,pos_args)

def func4(*pos_args):
    print(pos_args)

def func5(*pos_args,**kw_args):
    print("Positional:"+str(pos_args))
    print("Keywords:"+str(kw_args))

func1(1,2,3) # 1 2 3
func1(x=1,z=3,y=2) # 1 2 3
func1(1,z=3,y=2) # 1 2 3

func2(1,2) # 1 2 5
func2(y=2,x=1) # 1 2 5
func2(1,2,3) # 1 2 3
func2(x=1,z=3,y=2) # 1 2 3
func2(1,z=3,y=2) # 1 2 3

func3(1,2) # 1 2 ()
func3(y=2,x=1) # 1 2 () 
func3(1,2,5,6,7) # 1 2 (5,6,7)

func4() # ()
func4(1,2,3,5,6,7,8) # (1,2,3,5,6,7,8)

func5(1,2,3) # Positional:(1, 2, 3) , Keywords:{}
func5(x=1,y=2,z=3) # Positional:() , Keywords:{'z': 3, 'y': 2, 'x': 1}
func5(1,2,3,x=1,y=2,z=3) # Positional:(1, 2, 3) , Keywords:{'z': 3, 'y': 2, 'x': 1}

這個example.py可以用python2和python3執行,其參數行為都是一樣的,結果應與我每次呼叫函式的指令後面的註解一樣。

先來看看func1以及func2,大家千萬不要看到func2後面有"z=5",就認定z是由關鍵字參數設定,而x和y是由位置參數設定了,這個"z=5"只是代表z有一個default值而已,從程式碼下面對於func1和func2的呼叫,就可以明白不論是位置參數還是關鍵字參數都可以設定x,y,z這三個值,但要記得像"func2(x=1,y,z=5)"這樣的定義是不行的,因為python2和python3規定,沒有default值的參數不可以在有default值的參數後面,當然這件事和位置參數還是關鍵字參數本身是沒有太大關係的,只是令一個規定。

然後從func3開始出現了神奇的東西,就是"*pos_args",這在幹嘛勒,他就是說你可以傳任意長的"位置參數"給pos_args,pos_args會用tuple的型式存起來,當然因為是任意長,所以也可以不設定任何參數給pos_args,當沒用到pos_args時,func3的行為和fun1跟func2是一樣的,一樣可以用位置參數和關鍵字參數來設定x和y,但如果會用到pos_args,因為"位置參數在前,關鍵字參數在後",就不能用關鍵字參數來設定x和y,比如說像"func3(y=2,x=1,5,6,7)"是會出錯的,而"func3(5,6,7,y=2,x=1)"這樣也是不行的(因為位置參數"5,6,7"裏面的"5,6"會用來設定x,y,後面的"y=2,x=1"顯然重複設定)。

func4裏面只有一個"*pos_args",這代表一個函式可以完全接受任意長的"位置參數",一樣pos_args會用tuple的型式存起來,然後func4是完全不接受關鍵字參數設定的,因為func4裏面沒有一個參數可以給關鍵字參數設定。

有沒有發現只要函式定義後面有了"*pos_args"這樣的定義,這個函式就完全不接受關鍵字參數設定了,但其實還有一個例外,func5多了令一個東西"**kw_args",這是指函式也可以接受任意長的"關鍵字參數",這些都會被kw_args以dict的型式存起來,因此func5可以接受任意長的位置參數與關鍵字參數設定,而這些會各自存在pos_args和kw_args,這可以從藉著func5的行為而觀察到。

好了講了這麼一拖拉庫,到底要不要講python2和python3到底差在哪裡,以上的行為在python2和python3中都是一樣的阿!先再停一下哈哈,首先要了解為何python3還可以對參數的行為有所改進,上面這些範例算是介紹完python2的兩種參數類型的所有行為了,那python2在參數設定上究竟還有什麼缺失呢?

首先再回到func1,我們不可以用"func1(x=1,z=3,y=2,w=9)"呼叫func1,因為在func1的定義中,並沒有w這個參數,所以關鍵字參數設定只能侷限在x,y,z這三個名字,然後再看看func5,我們func5接受了任意長的"位置參數設定",也接受了任意長的"關鍵字參數設定",那有一天我想要定義一個func他接受任意長的"位置參數設定",但只想接受"有所侷限的關鍵字參數設定(像func1那樣)",似乎就無能為力了,那實務上有沒有這種例子呢?

有,就是python3的print函數,前面接受了任意長的"位置參數",而這些參數是用來print在螢幕上的,後面則是設定print出來的格式,比如說end和sep,有沒有看到我在版本差異雜談系列一的時候,嘗試用python2定義一個function來實現python3的end參數設定:

In python2 shell:
>>> import sys
>>> def print_with_end(end='\n',*args):
...     for arg in args:
...         print arg,
...     sys.stdout.write(end)
... 
>>> print_with_end('(the END)\n',1,'b',['x', 'y', 'z'])
1 b ['x', 'y', 'z'] (the END)
>>>     

這看起來其實是很傻逼的,因為python2的機制無法實現python3的print(),我前面的"end='\n'"實際上只是設一個default值,這樣我根本不能用關鍵字參數設定end,當然如果要在*args後面加個**kw_args來實現也是可以,但是超麻煩,而且不能限定只能用關鍵字參數設定end,sep等等參數。

python3的改進是其支援了像是"def print(args_for_print,end='\n',sep=' ',...)",這樣的定義方式,凡事在有符號的參數(比如說args_for_print)後面的參數都被稱為"Keyword-Only Arguments",你只能用關鍵字參數設定他們,而且不能用定義裡沒出現的關鍵字來指定參數,比如說print(1,2,'lalala',xyz=5),因為定義裡沒有xyz這個名字所以會出錯。

python3也支援了像是func(x,y,z,*,kwo1,kwo2)這樣的定義方式,其代表著x,y,z可以用位置參數或是關鍵字參數來設定,但是kwo1,kwo2因為是"Keyword-Only Arguments",所以不能用位置參數設定,這種定義方式對於維護程式的可讀性是很有幫助的,你可以對於一些比較難以理解意義的參數強制用關鍵字來設定,這樣讓別人引用你定義的參數可以乖乖的用關鍵字命名XD

參考資料:
https://stackoverflow.com/questions/1419046/python-normal-arguments-vs-keyword-arguments
https://www.python.org/dev/peps/pep-3102/


上一篇
版本差異雜談之五———懶得談open了&加個range充字數
下一篇
版本差異雜談之七———今天先讓自己input()一下
系列文
30天python雜談30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言