Как вызвать функцию со словарем, который содержит больше элементов, чем функция имеет параметры?

Я ищу лучший способ комбинировать функцию со словарем, который содержит больше элементов, чем функциональные входы

базовая ** распаковка 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 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

Мой вопрос: это хороший способ справиться с этой проблемой, или есть лучшая практика или есть механизм на языке, который я пропускаю, возможно?

+27
19 мар. '16 в 13:03
источник поделиться
6 ответов

Как насчет создания 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))

Что приятно об этом декораторе, так это то, что он является универсальным и многоразовым. И вам не нужно будет менять способ вызова и использовать ваши целевые функции.

+23
19 мар. '16 в 13:20
источник

Все эти ответы неверны.

Невозможно выполнить то, что вы просите, потому что функция может быть объявлена ​​следующим образом:

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

И здесь - это какой-то реальный код, который на самом деле это делает.

Вы можете спросить, зачем нам нужна последняя строка. Зачем передавать аргументы в суперкласс, который не принимает? Совместное множественное наследование. Если мой класс получает аргумент, он не распознает, он не должен проглатывать этот аргумент и не должен выходить из строя. Он должен передать аргумент в цепочку, чтобы другой класс, о котором я мог не знать, может справиться с этим. И если никто не справится с этим, тогда object.__init__() предоставит соответствующее сообщение об ошибке. К сожалению, другие ответы не будут обрабатывать это изящно. Они будут видеть **kwargs и либо не передают никаких аргументов, либо пропускают все из них, которые являются неправильными.

В нижней строке: нет общего способа узнать, является ли вызов функции законным, фактически не вызывая этот вызов функции. inspect является грубым приближением и полностью распадается перед лицом вариационных функций. Вариадика не означает "пройти все, что вам нравится"; это означает, что "правила слишком сложны, чтобы выразить в подписи". В результате, во многих случаях во многих случаях можно делать то, что вы пытаетесь сделать, всегда будут ситуации, когда нет правильного ответа.

+14
19 мар. '16 в 17:45
источник

Ваша проблема заключается в том, как вы определили свою функцию, ее следует определить следующим образом:

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/

+11
19 мар. '16 в 13:11
источник

Вы также можете использовать функцию decorator, чтобы отфильтровать те аргументы ключевого слова, которые не разрешены в вашей функции. Вы используете функцию signature new в 3.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

Из 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
+8
19 мар. '16 в 13:19
источник

Я бы сделал что-то вроде этого:

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
+3
19 мар. '16 в 13:11
источник

Это по-прежнему изменяет исходную функцию, но вы можете создать битбакет kwargs в конце списка аргументов:

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

foo(**{
    'a': 5,
    'b': 8,
    'c': '🐘'
}) # 13
+2
20 мар. '16 в 5:25
источник

Посмотрите другие вопросы по меткам или Задайте вопрос