2016-03-19 23 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 

我的問題是:這是處理這個問題的一個很好的方式,或者是有一個更好的做法還是有mechan我可能錯過了這種語言嗎?

回答

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是一個粗略的近似值,在可變參數函數面前完全崩潰。 Variadic並不意味着「通過任何你喜歡的東西」;它意味着「規則太複雜,無法在簽名中表達」。因此,雖然在很多情況下可能做你想做的事情,但總是會出現沒有正確答案的情況。

+1

謝謝凱文!我最終接受了alecxe的回答,因爲它對我的情況最直接有幫助,但是我發現你的閱讀也非常有趣,我同意必須認識到有一些重要的場景需要考慮'過濾器kwargs'方法不適用於何處工作。 –

2

這仍然是修改原有的功能,但你可以在參數列表的末尾創建一個kwargs到位桶:

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

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