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

Len*_*ken 26 python dictionary named-parameters kwargs

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

在这种情况下,基本**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'
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

ale*_*cxe 23

如何做一个装饰,将过滤器只允许关键字参数:

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))
Run Code Online (Sandbox Code Playgroud)

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


Kev*_*vin 14

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

你不可能做你想要的,因为函数可能会这样声明:

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

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

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

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
Run Code Online (Sandbox Code Playgroud)

这里是一些真实的代码实际上做到这一点.

你可能会问为什么我们需要最后一行.为什么要将参数传递给不带任何参数的超类? 合作多重继承.如果我的类得到一个它无法识别的参数,它不应该吞下那个参数,也不应该错误.它应该将参数传递给链,以便我可能不知道的另一个类可以处理它.如果没有人处理它,那么object.__init__()将提供适当的错误消息.不幸的是,其他答案不会优雅地处理.他们会看到**kwargs并传递任何参数或传递所有参数,这两者都是不正确的.

底线:没有一般方法来发现函数调用是否合法而没有实际进行函数调用. inspect是粗略的近似,并且在可变函数面前完全崩溃.Variadic并不意味着"通过你喜欢的任何东西"; 它意味着"规则过于复杂,无法在签名中表达." 因此,尽管在许多情况下可能会执行您尝试执行的操作,但总会出现无法正确答案的情况.


Yar*_*dan 11

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

def foo(**kwargs):
Run Code Online (Sandbox Code Playgroud)

然后在函数内部,您可以迭代发送到函数的参数数量,如下所示 -

if kwargs is not None:
        for key, value in kwargs.iteritems():
                do something
Run Code Online (Sandbox Code Playgroud)

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


sty*_*ane 8

您还可以使用装饰器函数来过滤掉函数中不允许的关键字参数.你使用signature3.3中的新功能返回你的功能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
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

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

演示:

>>> def foo(a, b):
...     return a + b
... 
>>> foo = decorator(foo)
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> foo(**d)
3
Run Code Online (Sandbox Code Playgroud)

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

>>> @decorator
... def foo(a, b):
...     return a + b
... 
>>> foo(**d)
3
Run Code Online (Sandbox Code Playgroud)

您还可以使用任意关键字参数定义函数**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
Run Code Online (Sandbox Code Playgroud)