简体   繁体   English

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

[英]Getting the keyword arguments actually passed to a Python method

I'm dreaming of a Python method with explicit keyword args: 我梦想着一个带有显式关键字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)

I want to get a dictionary of only those arguments the caller actually passed into the method, just like **kwargs , but I don't want the caller to be able to pass any old random args, unlike **kwargs . 我想得到一个只有调用者实际传入方法的那些参数的字典,就像**kwargs ,但我不希望调用者能够传递任何旧的随机args,这与**kwargs不同。

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

So: is there such an incantation? 所以:有这样的咒语吗? In my case, I happen to be able to compare each argument against its default to find the ones that are different, but this is kind of inelegant and gets tedious when you have nine arguments. 在我的情况下,我碰巧能够将每个参数与其默认值进行比较以找到不同的参数,但是当你有九个参数时,这有点不雅并且变得单调乏味。 For bonus points, provide an incantation that can tell me even when the caller passes in a keyword argument assigned its default value: 对于奖励积分,提供一个咒语,即使调用者传递了一个分配了默认值的关键字参数,也可以告诉我:

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

Tricksy! 调皮!

Edit: The (lexical) function signature has to remain intact. 编辑:(词法)函数签名必须保持不变。 It's part of a public API, and the primary worth of the explicit keyword args lies in their documentary value. 它是公共API的一部分,显式关键字args的主要价值在于它们的文档值。 Just to make things interesting. 只是为了让事情变得有趣。 :) :)

I was inspired by lost-theory's decorator goodness, and after playing about with it for a bit came up with this: 我受到了失去理论的装饰者善良的启发,在玩了一下之后想出了一点:

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

Which prints this: 打印这个:

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'

I'm happy with this. 我很高兴。 A more flexible approach is to pass the name of the attribute you want to use to the decorator, instead of hard-coding it to 'actual_kwargs', but this is the simplest approach that illustrates the solution. 更灵活的方法是将要使用的属性的名称传递给装饰器,而不是将其硬编码为“actual_kwargs”,但这是解释该解决方案的最简单方法。

Mmm, Python is tasty. 嗯,Python很好吃。

Here is the easiest and simplest way: 这是最简单,最简单的方法:

def func(a=None, b=None, c=None):
    args = locals().copy()
    print args

func(2, "egg")

This give the output: {'a': 2, 'c': None, 'b': 'egg'} . 这给出了输出: {'a': 2, 'c': None, 'b': 'egg'} The reason args should be a copy of the locals dictionary is that dictionaries are mutable, so if you created any local variables in this function args would contain all of the local variables and their values, not just the arguments. args应该是locals字典的副本是字典是可变的,所以如果你在这个函数中创建了任何局部变量, args将包含所有局部变量及其值,而不仅仅是参数。

More documentation on the built-in locals function here . 有关内置locals功能的更多文档可在此处运行

One possibility: 一种可能性:

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

IOW, it's very easy to check that the passed-in names are within the acceptable set and otherwise raise whatever you'd want Python to raise (TypeError, I guess;-). IOW,很容易检查传入的名称是否在可接受的集合内,否则会引发你想要Python引发的任何内容(TypeError,我猜;-)。 Pretty easy to turn into a decorator, btw. 很容易变成装饰,顺便说一下。

Another possibility: 另一种可能性

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

by making a unique object _sentinel you remove the risk that the caller might be accidentally passing None (or other non-unique default values the caller could possibly pass). 通过创建一个唯一的对象_sentinel您可以消除调用者可能意外传递None (或调用者可能传递的其他非唯一默认值)的风险。 This is all object() is good for, btw: an extremely-lightweight, unique sentinel that cannot possibly be accidentally confused with any other object (when you check with the is operator). 这是所有object()都适用于,btw:一个极其轻量级的独特标记,不可能与任何其他对象意外混淆(当您使用is运算符检查时)。

Either solution is preferable for slightly different problems. 对于稍微不同的问题,任何一种解决方

How about using a decorator to validate the incoming kwargs? 如何使用装饰器验证传入的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')

Gives this output: 给出这个输出:

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')

This is easiest accomplished with a single instance of a sentry object: 使用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

The nice thing about this approach is that, since we're using the "is" operator, the caller can pass an empty dict as the argument value, and we'll still pick up that they did not mean to pass it. 这种方法的好处在于,由于我们使用的是“is”运算符,调用者可以传递一个空的dict作为参数值,我们仍然会认为它们并不意味着传递它。 We also avoid nasty decorators this way, and keep our code a little cleaner. 我们也以这种方式避免讨厌的装饰,并保持我们的代码更清洁。

There's probably better ways to do this, but here's my take: 可能有更好的方法来做到这一点,但这是我的看法:

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)

and its output: 及其输出:

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

This could probably be converted to a decorator, but my decorators need work. 这可能会转换为装饰器,但我的装饰师需要工作。 Left as an exercise to the reader :P 留给读者的练习:P

Perhaps raise an error if they pass any *args? 如果他们通过任何* 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()))

It'd be simple to factor it into taking a list of varnames or a generic parsing function to use. 将它考虑到使用一个varnames列表或一个通用的解析函数是很简单的。 It wouldn't be too hard to make this into a decorator (python 3.1), too: 将它变成装饰器(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)

Note: i'm not sure how well this would work around already wrapped functions or functions that have *args or **kwargs already. 注意:我不确定这对于已经包含*args**kwargs已包装函数或函数的效果如何。

Magic is not the answer: 魔术不是答案:

def funky(a=None, b=None, c=None):
    for name, value in [('a', a), ('b', b), ('c', c)]:
        print name, value

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 统计arguments实际传给了一个python function - Count the number of arguments actually passed to a python function 获取传递给方法的所有名为 arguments 的字典 - Getting a dictionary of all named arguments passed to a method 在Python中的__getitem__方法中使用关键字参数 - Using keyword arguments in __getitem__ method in Python Python:参数没有正确传递给命令 - Python: Arguments not getting passed to command correctly Python 没有从 shell 脚本传递给它的 arguments - Python not getting arguments passed to it from shell script 以编程方式将混合关键字非关键字参数传递给Python中的绑定方法 - passing mixed keyword non keyword arguments programmatically to a bound method in Python 用关键字参数模拟方法 - Mocking a method with keyword arguments 通过cmd,len(sys.argv)= 1运行python脚本,与实际传递的参数数量无关 - Running a python script via cmd, len(sys.argv) = 1 independent of how many arguments actually passed Python:使用一个或多个关键字参数扩展方法的优雅习惯是什么? - Python: What is an elegant idiom for extending method with one or more keyword arguments? 如何获取Python中任何方法的关键字/位置参数 - How can I get keyword/positional arguments of any method in Python
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM