2009-05-06 13 views
12

Sto scrivendo un decoratore che deve chiamare altre funzioni prima di chiamare la funzione che sta decorando. La funzione decorata può avere argomenti posizionali, ma le funzioni che il decoratore chiamerà possono accettare solo argomenti di parole chiave. Qualcuno ha un modo pratico per convertire argomenti posizionali in argomenti di parole chiave?Python converte args in kwargs

so che posso ottenere un elenco dei nomi delle variabili della funzione decorato:

>>> def a(one, two=2): 
... pass 

>>> a.func_code.co_varnames 
('one', 'two') 

Ma io non riesco a capire come raccontare ciò che è stato approvato in modo posizionale e quello che era come parola chiave.

mio decoratore si presenta così:

class mydec(object): 
    def __init__(self, f, *args, **kwargs): 
     self.f = f 

    def __call__(self, *args, **kwargs): 
     hozer(**kwargs) 
     self.f(*args, **kwargs) 

C'è un modo diverso kwargs solo confronto e co_varnames, aggiungendo al kwargs tutto quanto non in là, e sperando per il meglio?

+1

Perché voi avete bisogno di sapere quali argomenti sono stati posizionale? –

+1

Perché ho bisogno di convertirli in kwargs per chiamare la funzione hozer. Questa funzione accetta solo kwargs, ma ha bisogno di conoscere tutti gli argomenti originariamente chiamati. Quindi, a seconda che le persone chiamino o meno la funzione decorata con argomenti posizionali o con nome, la funzione hozer può o meno avere tutti i dati di cui ha bisogno. – jkoelker

risposta

4

Nota: co_varnames include variabili locali e parole chiave. Questo probabilmente non avrà importanza, poiché zip tronca la sequenza più breve, ma può generare confusi messaggi di errore se si passa il numero errato di argomenti.

È possibile evitare questo con func_code.co_varnames[:func_code.co_argcount], ma è meglio utilizzare il modulo inspect. vale a dire:

import inspect 
argnames, varargs, kwargs, defaults = inspect.getargspec(func) 

Si consiglia inoltre di gestire il caso in cui la funzione definisce **kwargs o *args (anche se solo per sollevare un'eccezione quando viene utilizzato con il decoratore). Se questi sono impostati, il secondo e il terzo risultato da getargspec restituiranno il nome della variabile, altrimenti saranno Nessuno.

16

Qualsiasi argomento passato passivamente verrà passato a * args. E qualsiasi argomento passato come parola chiave verrà passato a ** kwargs. Se si dispone di valori e nomi args posizionali allora si può fare:

kwargs.update(dict(zip(myfunc.func_code.co_varnames, args))) 

per convertirli tutti in args di parole chiave.

+1

In effetti, 'kwargs.update (zip (myfunc.func_code.co_varnames, args))' è sufficiente. 'dict.update' gestisce anche i iterabili 2D. – obskyr

5

Bene, questo potrebbe essere eccessivo. L'ho scritto per il pacchetto dectools (su PyPi), quindi puoi ottenere aggiornamenti lì. Restituisce il dizionario tenendo conto di argomenti posizionali, di parole chiave e predefiniti. V'è una suite di test nella confezione (test_dict_as_called.py):

def _dict_as_called(function, args, kwargs): 
""" return a dict of all the args and kwargs as the keywords they would 
be received in a real function call. It does not call function. 
""" 

names, args_name, kwargs_name, defaults = inspect.getargspec(function) 

# assign basic args 
params = {} 
if args_name: 
    basic_arg_count = len(names) 
    params.update(zip(names[:], args)) # zip stops at shorter sequence 
    params[args_name] = args[basic_arg_count:] 
else: 
    params.update(zip(names, args))  

# assign kwargs given 
if kwargs_name: 
    params[kwargs_name] = {} 
    for kw, value in kwargs.iteritems(): 
     if kw in names: 
      params[kw] = value 
     else: 
      params[kwargs_name][kw] = value 
else: 
    params.update(kwargs) 

# assign defaults 
if defaults: 
    for pos, value in enumerate(defaults): 
     if names[-len(defaults) + pos] not in params: 
      params[names[-len(defaults) + pos]] = value 

# check we did it correctly. Each param and only params are set 
assert set(params.iterkeys()) == (set(names)|set([args_name])|set([kwargs_name]) 
           )-set([None]) 

return params 
+0

Ho visto questo copiato e incollato in dozzine di progetti open source. Questo dovrebbe essere spostato verso una funzione che le persone possono chiamare più facilmente! –

8

se si sta utilizzando Python> = 2.7 inspect.getcallargs fa questo per voi, fuori dalla scatola. Passeresti semplicemente la funzione decorata come primo argomento, e poi il resto degli argomenti esattamente come pianifichi di chiamarlo. Esempio:

>>> def f(p1, p2, k1=None, k2=None, **kwargs): 
...  pass 
>>> from inspect import getcallargs 

Sto pensando di fare f('p1', 'p2', 'p3', k2='k2', extra='kx1') (si noti che k1 viene passato positionally come p3), quindi ...

>>> call_args = getcallargs(f, 'p1', 'p2', 'p3', k2='k2', extra='kx1') 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'kwargs': {'extra': 'kx1'}} 

Se si conosce la funzione decorato non userà **kwargs , quindi quella chiave non apparirà nel dict, e il gioco è fatto (e suppongo che non ci sia il numero *args, dal momento che ciò interromperà il requisito che ogni cosa abbia un nome).Se fai avere **kwargs, come ho fatto in questo esempio, e si desidera includere con il resto degli argomenti con nome, ci vuole ancora una riga:

>>> call_args.update(call_args.pop('kwargs')) 
>>> call_args 
{'p2': 'p2', 'k2': 'k2', 'k1': 'p3', 'p1': 'p1', 'extra': 'kx1'} 
+0

Questo è il modo giusto per farlo (se hai Python 2.7 o successivo, che praticamente tutti fanno). –

Problemi correlati