2016-03-19 4 views
27

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

основной ** 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 

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

ответ

23

Как насчет делает 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)) 

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

11

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

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/

+1

Спасибо. Я должен был упомянуть, возможно, что я создаю рамки для программы, где другие люди (иногда относительные новички в python) создают функции. Следовательно, я бы предпочел, чтобы им не приходилось иметь дело с ** kwargs, а скорее с использованием базовых функций: foo (a, b); Я хотел бы скрыть эту сложность фильтрации kwargs в функциях полезности. –

3

Я хотел бы сделать что-нибудь г, как это:

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 
8

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

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

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

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 является грубым приближением и полностью разваливается перед лицом вариационных функций. Вариадика не означает «пройти все, что вам нравится»; это означает, что «правила слишком сложны, чтобы выразить в подписи». В результате, во многих случаях во многих случаях можно делать то, что вы пытаетесь сделать, всегда будут ситуации, когда правильного ответа нет.

+1

Спасибо, Кевин! В конце концов, я принял ответ alecxe, потому что он был наиболее полезен для моей ситуации, однако я нашел ваш очень интересный текст для чтения, и я согласен, что важно понять, что есть важные сценарии, чтобы рассмотреть, где подход «фильтр-кварты» не будет Работа. –

2

Это еще изменяя первоначальную функцию, но вы можете создать kwargs Bitbucket в конце списка аргументов:

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^