获取关键字参数实际上传递给Python方法

时间:2009-09-11 03:21:13

标签: python arguments keyword

我梦想着一个带有显式关键字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的主要价值在于它们的文档值。只是为了让事情变得有趣。 :)

8 个答案:

答案 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