你想做的事是不可能的。你永远不能用 Python 写这个:
shoppingcart(item='laptop', 100,200,300)
在 2.x 或 3.x 中,您将收到一个错误:
SyntaxError: non-keyword arg after keyword arg
首先,一点背景:
“关键字”参数类似于item='laptop'
,您可以在其中指定名称和值。“位置”或“非关键字”参数类似于100
,您只需在其中指定值。每当您调用函数时,都必须将关键字 args 放在所有位置 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 将引发 a 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
您提供 a一样,为您提供每个关键字参数的关键字映射到其值的关键字。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)
,它在您的代码中非常有用。但是,它使您的实现更加脆弱,使您的函数的行为对读者来说不太明显,并且通常不会让人感觉像 Python。所以,几乎总是最好避免这种情况,如果你想要的话,只让调用者使用真正的关键字参数,限制它们必须在最后。