python 在 argument 的處理上有很大的彈性, 以前在寫 C/C++ 時, 總會為了參數要怎麼傳, 怎麼樣才能讓參數穿透層層關卡到達最下層而煩惱, 程式要寫的有架構常常必須要分層次, 彼此之間要用定好 interface 隔開, 但間接造成的問題是, 如果在最上層要新增新的參數, 你很可能要一層一層的改下去, 也是很頭痛, 但 python 在這部份就比較有方法可以做到.
先看這兩篇在介紹 args 和 kwargs, args 是 list (有順序性), kwargs 是 dict (無順序性)
http://www.saltycrane.com/blog/2008/01/how-to-use-args-and-kwargs-in-python/
http://docs.python.org/tutorial/controlflow.html#keyword-arguments
第一個例子, 說明 *args 會把 argument 按照順序收集起來, 所以 test_var_args 除了第一個 "fargs" 一定要傳之外, 其它的參數可以任意的接在後來
>>> def test_var_args(farg, *args): ... print "formal arg:", farg ... for arg in args: ... print "another arg:", arg >>> test_var_args(1, "two", 3) formal arg: 1 another arg: two another arg: 3
第二個例子, 說明 **kwargs 會把 keyword argument 收集起來放進 kwargs 這個字典中, test_var_args 除了第一個 "fargs" 一定要傳之外, 其它的參數可以用 keyword argument 的方式加進去
>>> def test_var_kwargs(farg, **kwargs): ... print "formal arg:", farg ... for key in kwargs: ... print "another keyword arg: %s: %s" % (key, kwargs[key]) ... >>> test_var_kwargs(farg=1, myarg2="two", myarg3=3) formal arg: 1 another keyword arg: myarg2: two another keyword arg: myarg3: 3
最後是把兩個結合起來
>>> def test_vars(farg, *args, **kwargs): ... print "formal arg:", farg ... for arg in args: ... print "another arg:", arg ... for key in kwargs: ... print "another keyword arg: %s: %s" % (key, kwargs[key]) ... >>> test_vars(1, 2, 3, myarg4="four", myarg5=5) formal arg: 1 another arg: 2 another arg: 3 another keyword arg: myarg4: four another keyword arg: myarg5: 5
所以有了 args 和 kwargs, python 的函式在傳參數時就可以做到不定個數、不定長度. 那就可以玩一些變化讓這些參數具有穿透力. 這有什麼用處呢? 在某些情況, 假設你有 f1, 內部會用到 f2, 你為了讓呼叫的人也可以控制到 f2, 所以你必須也要在 f1 參數上也加上 f2 的參數. 就像下面這個例子
>>> def f1(a, b=1): ... f2(b) ... >>> def f2(b=1): pass ...
這樣的寫法會有什麼困擾呢?
第一個是有關於預設值, 如果當我們呼叫 f1 時, 預期在不給 b 的狀況下, 能夠直接使用 f2 的設定值, 我們就只能在 f1 中也針對 b 設定一樣的預設值, 否則就會不同步, 產生錯誤的行為
第二個是如果當 f2 增加參數時或改變參數預設值時, f1 也必須要跟著修改
所以我們可以利用 args/kwargs 來讓參數有穿透力, 我們在 f1 只關心 a 這個參數, 於是我們把 a 拿走, 剩下的全部傳進 f2, 而 f2 只需要 b, 於是它把 b 拿走, 剩下的傳進 f3.
>>> def f3(c): ... print c ... >>> def f2(b, *args, **kwargs): ... print b ... f3(*args, **kwargs) ... >>> def f1(a, *args, **kwargs): ... print a ... f2(*args, **kwargs) ... >>> f1(1, 2, 3) 1 2 3 >>> f1(1, 2, c=3) 1 2 3 >>> f1(1, b=2, c=3) 1 2 3
如果我們在 f1 新增一個參數 d, 為了向前相容, 所以我們給他一個預設值, 所以原來的程式碼也還可以繼續使用, 新的程式碼如果需要修改參數 d, 也只需要在呼叫時, 多一個 keyword argument 即可, f2/f3 完全不需要修改, 真的是很方便.
>>> def f1(c, d=2): ... print c,d ... >>> f3(3, c=5, b=4) 3 4 5 2 >>> f3(3, c=5, b=4, d=6) 3 4 5 6
再來下一個問題:是否有辦法觀察一個函式的參數有那些? 而且是否有預設值, 我們可以利用 inspect 這個模組.
http://stackoverflow.com/questions/196960/can-you-list-the-keyword-arguments-a-python-function-receives
http://docs.python.org/library/inspect.html?highlight=inspect#inspect
>>> def func(a,b,c=42, *args, **kwargs): pass ... >>> inspect.getargspec(func) ArgSpec(args=['a', 'b', 'c'], varargs='args', keywords='kwargs', defaults=(42,))
除了原來連結寫的幾個函式很有用處之外, 我也寫了一個小工具, 主要是用來應付如果要乎叫的函式不能傳進 kwargs 時, 就必須要先把 kwargs 過濾過, 把可接受的部份留下, 然後去除掉不能使用的部份
def filter_args(func, kwargs): args, varargs, varkw, defaults = inspect.getargspec(func) args_with_default = args[-len(defaults):] valid_kw = dict() if not varkw: for arg in kwargs: # remove unaccepted argument if arg not in args: continue # argument = None but with default value elif not kwargs[arg] and arg in args_with_default: continue valid_kw.update({arg:kwargs[arg]}) return valid_kw
這在 command line 的參數處理滿有用處的,
def shellcmd(func, *argv, **kwargs): kwargs = filter_args(func, kwargs) func(*argv, **kwargs)