你想做什麼是不可能的。你永遠不能用Python寫:
shoppingcart(item='laptop', 100,200,300)
在任一2.x或3.x中,你會得到一個錯誤:
SyntaxError: non-keyword arg after keyword arg
首先,一點背景:
A「關鍵字「參數是類似於item='laptop'
,您可以在其中指定名稱和值。 「位置」或「非關鍵字」參數與100
類似,您只需指定值即可。無論何時調用函數,都必須在所有位置參數後面加上關鍵字args。所以,你可以寫shoppingcart(100, 200, 300, item='laptop')
,但是你不能寫shoppingcart(item='laptop', 100, 200, 300)
。
當你理解關鍵字參數的處理方式時,這會更有意義。讓我們以一個非常簡單的情況下,用一套固定的參數,沒有缺省值,使一切更簡單:
def shoppingcart(item, price):
print item, price
無論我把這種與shoppingcart('laptop', 200)
,shoppingcart(price=200, item='laptop')
,或任何其他可能性,你可以計算出如何參數在函數體中到達item
和price
。除了shoppingcart(price=200, 'laptop')
之外,Python不知道laptop
應該是什麼 - 它不是第一個參數,它沒有給出明確的關鍵字item
,所以它不能是item
。但它也不能是price
,因爲我已經知道了。如果你認真思考,每當我嘗試在位置參數之前傳遞關鍵字參數時,都會遇到同樣的問題。在更復雜的情況下,很難看出原因,但在最簡單的情況下甚至不起作用。
因此,Python將引發一個SyntaxError: non-keyword arg after keyword arg
,甚至沒有看你的函數是如何定義的。所以沒有辦法改變你的函數定義來完成這個工作;這是不可能的。
但是,如果您將item
移動到參數列表的末尾,那麼我可以撥打shoppingcart(100, 200, 300, item='computer')
,一切都會好的,對不對?遺憾的是,不,因爲您使用*args
來吸收100, 200, 300
,並且您不能在*args
之後放置任何明確的參數。事實證明,這個限制沒有原則性的原因,所以他們在Python 3中放棄了它 - 但是如果你仍然使用2.7,你必須忍受它。但至少有一個解決方法。
解決方法是使用**kwargs
來吸收所有關鍵字參數並自己解析它們,這與使用*args
吸收所有位置參數的方式相同。就像*args
得到一個帶有位置參數的list
一樣,**kwargs
會得到一個dict
,每個關鍵字參數的關鍵字映射到它的值。
所以:
>>> def shoppingcart(*args, **kwargs):
... print args, kwargs
>>> shoppingcart(100, 200, 300, item='laptop')
[100, 200, 300], {'item': 'laptop'}
如果你想item
是強制性的,你只需訪問這樣的:
item = kwargs['item']
如果你希望它是可選的,默認值,你做這樣的:
item = kwargs.get('item', 'computer')
但這裏有一個問題:你只是想接受一個可選item
的論點,但現在你實際上接受任何關鍵字參數。如果試圖簡單地用功能,你會得到一個錯誤:
>>> def simplefunc(a, b, c):
... print a, b, c
>>> simplefunc(1, 2, 3, 4)
TypeError: simplefunc() takes exactly 3 arguments (4 given)
>>> simplefunc(a=1, b=2, c=3, d=4, e=5)
TypeError: simplefunc() got an unexpected keyword argument 'd'
有與**kwargs
來模擬這是一個非常簡單的方法。請記住,這只是一個正常的dict
,所以如果在解析它們時取出預期的關鍵字,則應該沒有任何遺漏 - 如果存在,則調用方傳遞了意外的關鍵字參數。如果你不想這樣做,你可以提出一個錯誤。你可以這樣寫:
def shoppingcart(*prices, **kwargs):
for key in kwargs:
if key != 'item':
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
item = kwargs.get('item', 'computer')
print item, prices
但是,有一個方便的捷徑。 dict.pop
方法的作用與dict.get
類似,但除了將值返回給您之外,它還將其從字典中移除。當你刪除item
(如果有的話),如果還有剩餘的東西,那是錯誤的。所以,你可以只是這樣做:
def shoppingcart(*args, **kwargs):
item = kwargs.pop('item', 'computer')
if kwargs: # Something else was in there besides item!
raise TypeError("shoppingcart() got unexpected keyword argument(s) '%s' % kwargs.keys()[0]`)
除非你正在構建一個可重用的庫,你可以用的東西比這整個if
/raise
事情簡單,只是做assert not kwargs
脫身;沒有人會在意你得到的是AssertionError
而不是TypeError
。這就是lqc的解決方案所做的。
回顧一下,如何編寫函數並不重要;它永遠不會被稱爲你想要的方式。但是,如果您願意將item
移動到最後,您可以將它寫入並以合理的方式調用它(即使它不像Python 3版本那樣簡單或可讀)。
對於一些罕見的情況,還有其他方法可以去。例如,如果價格始終是數字,項目總是字符串,你可以改變你的代碼做這樣的事情:
def shoppingcart(*prices):
if prices and isinstance(prices[0], basestring):
item = prices.pop(0)
else:
item = 'computer'
print item
for price in prices:
print price
現在,你可以這樣做:
>>> shoppingcart(100, 200, 300)
computer
100
200
300
>>> shoppingcart('laptop', 100, 200, 300) # notice no `item=` here
laptop
100
200
300
這基本上Python是用來實現slice(start=None, stop, step=None)
的技巧,它在你的代碼中非常偶然。但是,這會讓你的實現變得更加脆弱,使得你的函數的行爲對讀者來說更加不明顯,並且通常不會感覺到Pythonic。所以,避免這種情況幾乎總是最好的,只要讓調用者使用真正的關鍵字參數(如果這是你想要的),並限制它們必須結束。
Python 2不會讓你寫'shoppingcart(item ='laptop',100,200,300)'。無論你如何編寫函數都沒關係,因爲你不能以這種方式調用它。 – abarnert
至於錯誤的含義:一個「關鍵字arg」就像'item ='laptop'',你指定名稱和值;一個「位置參數」或者「非關鍵字參數」類似於'100',你只需要指定一個值,當你調用一個函數時,必須在關鍵字args後面加上所有的位置參數,所以,你可以編寫'購物車(100,200,300,項目=「筆記本電腦」)' - 和小葉女貞的代碼,如果你做的工作。 – abarnert