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

27 投票
8 回答
11128 浏览
提问于 2025-04-15 14:14

我在想一个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那样,但我不想让调用者随便传入任何参数,这点和**kwargs不一样。

>>> func(b=2)
b: 2
>>> func(a=3, c=5)
a: 3
c: 5

那么:有没有这样的办法呢?在我的情况下,我可以把每个参数和它的默认值进行比较,以找出不同的参数,但这样做有点笨拙,尤其是当参数有九个的时候,真的很麻烦。如果能提供一个方法,能够告诉我即使调用者传入了默认值的关键字参数,那就更好了:

>>> func(a=None)
a: None

真是个难题!

编辑:函数的签名必须保持不变。这是一个公共API的一部分,明确的关键字参数的主要价值在于它们的文档价值。为了让事情更有趣。:)

8 个回答

7

一种可能的解决方案是:

def f(**kw):
  acceptable_names = set('a', 'b', 'c')
  if not (set(kw) <= acceptable_names):
    raise WhateverYouWantException(whatever)
  ...proceed...

换句话说,你可以很简单地检查传入的名字是否在允许的范围内,如果不在,就可以让Python抛出你想要的错误(我想是TypeError吧;-)。顺便说一下,这个功能很容易变成一个装饰器。

另一种可能的解决方案是:

_sentinel = object():
def f(a=_sentinel, b=_sentinel, c=_sentinel):
   ...proceed with checks `is _sentinel`...

通过创建一个独特的对象_sentinel,你可以避免调用者不小心传入None(或者其他可能传入的非唯一默认值)。顺便提一下,这个object()的作用就是提供一个非常轻量级、独特的哨兵对象,它绝对不会和其他对象混淆(当你用is运算符检查时)。

这两种解决方案适用于稍微不同的问题,各有优劣。

20

这里有一个最简单的方法:

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函数的更多说明,可以在这里找到。

31

我受到lost-theory的装饰器灵感启发,玩了一段时间后想出了这个:

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真不错。

撰写回答