我梦想着一个带有显式关键字args的Python方法:
def func(a=None, b=None, c=None):
for arg, val in magic_arg_dict.items(): # Where do I get the magic?
print '%s: %s' % (arg, val)
我想得到一个字典,只包含调用者实际传入方法的那些参数,就像**kwargs
一样,但是我不希望调用者能够传递任何旧的随机args,这与{{{ 1}}。
**kwargs
那么:有这样的咒语吗?在我的情况下,我碰巧能够将每个参数与其默认值进行比较以找到不同的参数,但是当你有九个参数时,这有点不雅并且变得单调乏味。对于奖励积分,提供一个咒语,即使调用者传递了一个分配了默认值的关键字参数,也可以告诉我:
>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5
调皮!
编辑:(词汇)函数签名必须保持不变。它是公共API的一部分,显式关键字args的主要价值在于它们的文档值。只是为了让事情变得有趣。 :)
答案 0 :(得分:25)
我受到了失去理论的装饰者善良的启发,并且在玩了一下之后想出了一点:
def actual_kwargs():
"""
Decorator that provides the wrapped function with an attribute 'actual_kwargs'
containing just those keyword arguments actually passed in to the function.
"""
def decorator(function):
def inner(*args, **kwargs):
inner.actual_kwargs = kwargs
return function(*args, **kwargs)
return inner
return decorator
if __name__ == "__main__":
@actual_kwargs()
def func(msg, a=None, b=False, c='', d=0):
print msg
for arg, val in sorted(func.actual_kwargs.iteritems()):
print ' %s: %s' % (arg, val)
func("I'm only passing a", a='a')
func("Here's b and c", b=True, c='c')
func("All defaults", a=None, b=False, c='', d=0)
func("Nothin'")
try:
func("Invalid kwarg", e="bogon")
except TypeError, err:
print 'Invalid kwarg\n %s' % err
打印出来:
I'm only passing a a: a Here's b and c b: True c: c All defaults a: None b: False c: d: 0 Nothin' Invalid kwarg func() got an unexpected keyword argument 'e'
我对此很满意。更灵活的方法是将要使用的属性的名称传递给装饰器,而不是将其硬编码为“actual_kwargs”,但这是说明解决方案的最简单方法。
嗯,Python很好吃。答案 1 :(得分:15)
这是最简单最简单的方法:
def func(a=None, b=None, c=None):
args = locals().copy()
print args
func(2, "egg")
这给出了输出:{'a': 2, 'c': None, 'b': 'egg'}
。
args
应该是locals
字典的副本的原因是字典是可变的,因此如果在此函数中创建任何局部变量args
将包含所有局部变量及其值,而不只是争论。
有关内置locals
函数here的更多文档。
答案 2 :(得分:5)
一种可能性:
def f(**kw):
acceptable_names = set('a', 'b', 'c')
if not (set(kw) <= acceptable_names):
raise WhateverYouWantException(whatever)
...proceed...
IOW,很容易检查传入的名称是否在可接受的集合内,否则会引发你想要Python引发的任何内容(TypeError,我猜;-)。很容易变成装饰,顺便说一句。
另一种可能性:
_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
...proceed with checks `is _sentinel`...
通过创建唯一对象_sentinel
,您可以消除调用者可能意外传递None
的风险(或调用者可能传递的其他非唯一默认值)。这是object()
所有的好处,顺便说一下:一个极其轻量级的独特标记,不可能与任何其他对象意外混淆(当您使用is
运算符检查时)。
对于略有不同的问题,这两种解决方案都是优选的。
答案 3 :(得分:2)
如何使用装饰器来验证传入的kwargs?
def validate_kwargs(*keys):
def entangle(f):
def inner(*args, **kwargs):
for key in kwargs:
if not key in keys:
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
return f(*args, **kwargs)
return inner
return entangle
###
@validate_kwargs('a', 'b', 'c')
def func(**kwargs):
for arg,val in kwargs.items():
print arg, "->", val
func(b=2)
print '----'
func(a=3, c=5)
print '----'
func(d='not gonna work')
给出这个输出:
b -> 2
----
a -> 3
c -> 5
----
Traceback (most recent call last):
File "kwargs.py", line 20, in <module>
func(d='not gonna work')
File "kwargs.py", line 6, in inner
raise ValueError("Received bad kwarg: '%s', expected: %s" % (key, keys))
ValueError: Received bad kwarg: 'd', expected: ('a', 'b', 'c')
答案 4 :(得分:1)
使用sentry对象的单个实例最容易实现:
# Top of module, does not need to be exposed in __all__
missing = {}
# Function prototype
def myFunc(a = missing, b = missing, c = missing):
if a is not missing:
# User specified argument a
if b is missing:
# User did not specify argument b
这种方法的好处在于,由于我们使用的是“is”运算符,调用者可以传递一个空的dict作为参数值,我们仍然会发现它们并不意味着传递它。我们也以这种方式避免讨厌的装饰,并保持我们的代码更清洁。
答案 5 :(得分:0)
这可能是更好的方法,但这是我的看法:
def CompareArgs(argdict, **kwargs):
if not set(argdict.keys()) <= set(kwargs.keys()):
# not <= may seem weird, but comparing sets sometimes gives weird results.
# set1 <= set2 means that all items in set 1 are present in set 2
raise ValueError("invalid args")
def foo(**kwargs):
# we declare foo's "standard" args to be a, b, c
CompareArgs(kwargs, a=None, b=None, c=None)
print "Inside foo"
if __name__ == "__main__":
foo(a=1)
foo(a=1, b=3)
foo(a=1, b=3, c=5)
foo(c=10)
foo(bar=6)
及其输出:
Inside foo Inside foo Inside foo Inside foo Traceback (most recent call last): File "a.py", line 18, in foo(bar=6) File "a.py", line 9, in foo CompareArgs(kwargs, a=None, b=None, c=None) File "a.py", line 5, in CompareArgs raise ValueError("invalid args") ValueError: invalid args
这可能会被转换为装饰者,但我的装饰者需要工作。作为练习留给读者:P
答案 6 :(得分:0)
如果他们通过任何* args,可能会引发错误?
def func(*args, **kwargs):
if args:
raise TypeError("no positional args allowed")
arg1 = kwargs.pop("arg1", "default")
if kwargs:
raise TypeError("unknown args " + str(kwargs.keys()))
将它考虑为使用一个varnames列表或一个通用的解析函数是很简单的。将它变成装饰器(python 3.1)也不会太难:
def OnlyKwargs(func):
allowed = func.__code__.co_varnames
def wrap(*args, **kwargs):
assert not args
# or whatever logic you need wrt required args
assert sorted(allowed) == sorted(kwargs)
return func(**kwargs)
注意:我不确定这对于已经包含*args
或**kwargs
的函数或函数的效果如何。
答案 7 :(得分:0)
魔术不是答案:
def funky(a=None, b=None, c=None):
for name, value in [('a', a), ('b', b), ('c', c)]:
print name, value