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

How to call a function with a dictionary that contains more items than the function has parameters?

我正在寻找将函数与字典组合的最佳方法包含的项多于函数的输入

在这种情况下,基本的 **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

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

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

def foo(**kwargs):

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

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

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

我会这样做:

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

您也可以使用 decorator function to filter out those keyword arguments that are not allowed in you function. Of you use the signature function new in 3.3 to return your function 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

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

演示:

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

制作一个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))

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

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

无法执行您要求的操作,因为该函数可能声明如下:

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 是一些实际执行此操作的真实代码。

您可能会问为什么我们需要最后一行。为什么要将参数传递给一个不接受任何参数的 superclass? Cooperative multiple inheritance。如果我的 class 得到一个它无法识别的参数,它不应该吞下那个参数,也不应该出错。它应该将参数传递到链中,以便另一个我可能不知道的 class 可以处理它。如果没有人处理它,那么 object.__init__() 将提供适当的错误消息。不幸的是,其他答案不会优雅地处理这个问题。他们将看到 **kwargs 并且要么不传递任何参数,要么传递所有参数,这都是不正确的。

底线:没有通用的方法可以在不实际进行函数调用的情况下发现函数调用是否合法。 inspect 是一个粗略的近似值,在可变参数函数面前完全崩溃。 Variadic 并不意味着 "pass whatever you like";这意味着 "the rules are too complex to express in a signature." 因此,虽然在许多情况下可能会做您想做的事情,但总会出现没有正确答案的情况。

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

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

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