如何使用包含比函数参数更多的项目的字典调用函数?

Posted

技术标签:

【中文标题】如何使用包含比函数参数更多的项目的字典调用函数?【英文标题】:How to call a function with a dictionary that contains more items than the function has parameters? 【发布时间】:2016-07-06 06:03:10 【问题描述】:

我正在寻找将函数与字典相结合的最佳方法包含比函数输入更多的项目

在这种情况下,基本的 **kwarg 解包失败:

def foo(a,b):
    return a + b

d = 'a':1,
     'b':2,
     'c':3

foo(**d)
--> TypeError: foo() got an unexpected keyword argument 'c'

经过一番研究,我想出了以下方法:

import inspect

# utilities
def get_input_names(function):
    '''get arguments names from function'''
    return inspect.getargspec(function)[0]

def filter_dict(dict_,keys):
    return k:dict_[k] for k in keys

def combine(function,dict_):
    '''combine a function with a dictionary that may contain more items than the function's inputs '''
    filtered_dict = filter_dict(dict_,get_input_names(function))
    return function(**filtered_dict)

# examples
def foo(a,b):
    return a + b

d = 'a':1,
     'b':2,
     'c':3

print combine(foo,d)
--> 3

我的问题是:这是处理这个问题的好方法,还是有更好的做法,或者语言中是否存在我可能缺少的机制?

【问题讨论】:

【参考方案1】:

您还可以使用decorator function 过滤掉那些在您的函数中不允许使用的关键字参数。使用 3.3 中新增的 signature 函数来返回你的函数 Signature

from inspect import signature
from functools import wraps


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        sig = signature(func)
        result = func(*[kwargs[param] for param in sig.parameters])
        return result
    return wrapper

从 Python 3.0 开始,您可以使用 getargspec自 3.0 版起已弃用

import inspect


def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        argspec = inspect.getargspec(func).args
        result = func(*[kwargs[param] for param in argspec])
            return result
    return wrapper

要应用你的 decorate 现有函数,你需要将你的函数作为参数传递给你的装饰器:

演示:

>>> def foo(a, b):
...     return a + b
... 
>>> foo = decorator(foo)
>>> d = 'a': 1, 'b': 2, 'c': 3
>>> foo(**d)
3

要将您的装饰器应用于新功能,只需使用@

>>> @decorator
... def foo(a, b):
...     return a + b
... 
>>> foo(**d)
3

您还可以使用任意关键字参数**kwargs 定义您的函数。

>>> def foo(**kwargs):
...     if 'a' in kwargs and 'b' in kwargs:
...         return kwargs['a'] + kwargs['b']
... 
>>> d = 'a': 1, 'b': 2, 'c': 3
>>> foo(**d)
3

【讨论】:

【参考方案2】:

这仍然是在修改原来的函数,但是你可以在参数列表的末尾创建一个 kwargs bitbucket:

def foo(a, b, **kwargs):
    return a + b

foo(**
    'a': 5,
    'b': 8,
    'c': '?'
) # 13

【讨论】:

【参考方案3】:

所有这些答案都是错误的。

不可能按照你的要求做,因为函数可能是这样声明的:

def foo(**kwargs):
    a = kwargs.pop('a')
    b = kwargs.pop('b')
    if kwargs:
        raise TypeError('Unexpected arguments: %r' % kwargs)

现在,为什么会有人写这个?

因为他们并不提前知道所有的论点。这是一个更现实的案例:

def __init__(self, **kwargs):
    for name in self.known_arguments():
        value = kwargs.pop(name, default)
        self.do_something(name, value)
    super().__init__(**kwargs)  # The superclass does not take any arguments

here 是一些实际执行此操作的真实代码。

你可能会问为什么我们需要最后一行。为什么将参数传递给不带任何参数的超类? Cooperative multiple inheritance。如果我的班级得到一个它不认识的论点,它不应该吞下那个论点,也不应该出错。它应该将参数向上传递,以便我可能不知道的另一个类可以处理它。如果没有人处理它,那么object.__init__() 将提供适当的错误消息。不幸的是,其他答案不会优雅地处理这个问题。他们会看到**kwargs,要么不传递参数,要么传递所有参数,这都是不正确的。

底线:没有一般的方法可以在不实际调用函数的情况下发现函数调用是否合法。 inspect 是一个粗略的近似,在可变参数函数面前完全崩溃了。可变参数并不意味着“随心所欲”;它的意思是“规则太复杂,无法在签名中表达”。因此,虽然在许多情况下可能会做你想做的事情,但总会有没有正确答案的情况。

【讨论】:

谢谢凯文!我最后接受了alecxe的回答,因为它对我的情况最直接有帮助,但是我发现你的阅读也很有趣,我同意认识到有重要的场景需要考虑“过滤器kwargs”方法不会工作。【参考方案4】:

制作一个decorator怎么样,只过滤允许的关键字参数

import inspect


def get_input_names(function):
    '''get arguments names from function'''
    return inspect.getargspec(function)[0]


def filter_dict(dict_,keys):
    return k:dict_[k] for k in keys


def filter_kwargs(func):
   def func_wrapper(**kwargs):
       return func(**filter_dict(kwargs, get_input_names(func)))
   return func_wrapper


@filter_kwargs
def foo(a,b):
    return a + b


d = 'a':1,
     'b':2,
     'c':3

print(foo(**d))

这个装饰器的好处是它是通用的和可重用的。而且您不需要更改调用和使用目标函数的方式。

【讨论】:

【参考方案5】:

我会这样做:

def combine(function, dictionary):
    return function(**key:value for key, value in dictionary.items()
                    if key in inspect.getargspec(function)[0]
    )

用途:

>>> def this(a, b, c=5):
...     print(a, b, c)
...
>>> combine(this, 'a': 4, 'b': 6, 'c': 6, 'd': 8)
4 6 6
>>> combine(this, 'a': 6, 'b': 5, 'd': 8)
6 5 5

【讨论】:

【参考方案6】:

你的问题在于你定义你的函数的方式,它应该像这样定义 -

def foo(**kwargs):

然后在函数内部,您可以像这样迭代发送给函数的参数数量 -

if kwargs is not None:
        for key, value in kwargs.iteritems():
                do something

您可以在这篇文章中找到有关使用 **kwargs 的更多信息 - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/

【讨论】:

谢谢。我也许应该提到我正在为其他人(有时是 python 中的相关新手)正在创建函数的程序构建框架。因此,我希望他们不必处理 **kwargs ,而是使用基本函数输入: foo(a,b);我想在实用程序函数中隐藏这个 **kwargs 过滤复杂性。

以上是关于如何使用包含比函数参数更多的项目的字典调用函数?的主要内容,如果未能解决你的问题,请参考以下文章

将字典作为关键字参数传递给函数

45.不定长参数字典

如何使用局部变量作为参数调用foreach中的函数?

JS 闭包

函数探幽

Lua - 使用定义的更多参数调用函数