2016-03-19 26 views
27

Sto cercando il modo migliore per combinare una funzione con un dizionario che contiene più elementi di input della funzioneCome chiamare una funzione con un dizionario che contiene più voci di quanti parametri ha la funzione?

base ** kwarg disimballaggio non riesce in questo caso:

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

d = {'a':1, 
    'b':2, 
    'c':3} 

foo(**d) 
--> TypeError: foo() got an unexpected keyword argument 'c' 

Dopo alcune ricerche sono venuto con il seguente approccio:

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 

la mia domanda è: è questo un buon modo di affrontare questo problema, o c'è una pratica migliore o c'è un Mechan ism nella lingua che mi manca forse?

risposta

23

Che ne dite di fare un decorator che avrebbe filtro permesso argomenti chiave solo:

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

Ciò che è bello su questo decoratore è che è generico e riutilizzabile. E non avresti bisogno di cambiare il modo in cui chiami e utilizzare le funzioni di destinazione.

11

Il tuo problema è il modo in cui è stata definita la funzione, occorre definire in questo modo -

def foo(**kwargs): 

E poi all'interno della funzione è possibile iterare il numero di argomenti inviati alla funzione in questo modo -

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

È possibile trovare maggiori informazioni sull'utilizzo ** kwargs in questo post - http://pythontips.com/2013/08/04/args-and-kwargs-in-python-explained/

+1

Grazie. Avrei dovuto menzionare forse che sto costruendo un framework per un programma in cui altre persone (talvolta novizi relativi in ​​python) stanno creando le funzioni. Quindi, preferirei che non dovessero avere a che fare con i ** kwargs, ma piuttosto usando gli input delle funzioni di base: foo (a, b); Mi piacerebbe nascondere questo ** kwargs che filtra la complessità nelle funzioni di utilità. –

3

vorrei fare qualcosa g come questo:

def combine(function, dictionary): 
    return function(**{key:value for key, value in dictionary.items() 
        if key in inspect.getargspec(function)[0]} 
    ) 

Usa:

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

È possibile anche utilizzare un decorator function per filtrare quelle argomenti chiave che non sono consentiti in si lavora. Di utilizzare la signature nuova funzione in 3.3 per restituire la funzione 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 

Da Python 3.0 è possibile utilizzare getargspec che è deprecato dalla versione 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 

Per applicare la vostra decorare un funzione esistente è necessario passare la funzione come argomento al decoratore:

Demo:

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

per applicare il proprio decoratore a una nuova funzione è sufficiente utilizzare @

>>> @decorator 
... def foo(a, b): 
...  return a + b 
... 
>>> foo(**d) 
3 

È inoltre possibile definire la funzione utilizzando parole chiave arbitrarie argomenti **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

Tutte queste risposte sono errate.

Non è possibile fare ciò che si sta chiedendo, perché la funzione potrebbe essere dichiarato in questo modo:

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

Ora, perché mai qualcuno dovrebbe scrivere che?

Perché non conoscono tutti gli argomenti in anticipo. Ecco un caso più realistico:

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 

E here è un codice del mondo reale che fa in realtà questo.

Si potrebbe chiedere perché abbiamo bisogno dell'ultima riga. Perché passare argomenti a una superclasse che non ne richiede? Cooperative multiple inheritance. Se la mia classe ottiene un argomento che non riconosce, non dovrebbe ingoiare quell'argomento, né dovrebbe disconoscere. Dovrebbe passare la discussione sulla catena in modo che un'altra classe di cui non posso sapere possa gestirla. E se nessuno lo gestisce, allora object.__init__() fornirà un messaggio di errore appropriato. Sfortunatamente, le altre risposte non lo gestiranno con grazia. Vederanno **kwargs e non passeranno argomenti o passeranno tutti, che sono entrambi errati.

La riga di fondo: non esiste un modo generale per scoprire se una chiamata di funzione è legale senza effettivamente effettuare quella chiamata di funzione. inspect è un'approssimazione approssimativa, e cade completamente a pezzi di fronte alle funzioni variadiche. Variadic non significa "passare ciò che vuoi"; significa "le regole sono troppo complesse per esprimere in una firma". Di conseguenza, sebbene in molti casi sia possibile fare ciò che stai cercando di fare, ci saranno sempre situazioni in cui non esiste una risposta corretta.

+1

Grazie Kevin! Alla fine ho accettato la risposta di Alecxe perché è stata più direttamente utile per la mia situazione, tuttavia ho trovato una tua lettura molto interessante, e sono d'accordo che è essenziale rendersi conto che ci sono scenari importanti da considerare dove l'approccio 'filter kwargs' non lavoro. –

2

questo è ancora modificando la funzione originale, ma è possibile creare un bitbucket kwargs alla fine della lista degli argomenti:

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

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