2009-05-06 9 views
12

J'écris un décorateur qui doit appeler d'autres fonctions avant l'appel de la fonction qu'il décore. La fonction décorée peut avoir des arguments de position, mais les fonctions appelées par le décorateur ne peuvent accepter que des arguments de mots-clés. Est-ce que quelqu'un a un moyen pratique de convertir les arguments positionnels en arguments mot-clé?Python convertir args en kwargs

Je sais que je peux obtenir une liste des noms de variables de la fonction décorée:

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

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

Mais je ne peux pas comprendre comment dire ce qui a été passé en positionnelle et ce qui était comme mot-clé.

Mon décorateur ressemble à ceci:

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

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

est-il un moyen autre que la simple comparaison kwargs et co_varnames, en ajoutant à kwargs quoi que ce soit pas là-dedans, et espérer le meilleur?

+1

Pourquoi vous vous avez besoin de savoir ce que args étaient positionnel? –

+1

Parce que j'ai besoin de les convertir en kwargs pour appeler la fonction hozer. Cette fonction accepte uniquement kwargs, mais elle doit connaître tous les arguments appelés à l'origine. Donc, selon que les gens appellent ou non la fonction décorée avec des arguments positionnels ou nommés, la fonction hozer peut ou non obtenir toutes les données dont elle a besoin. – jkoelker

Répondre

4

Remarque - co_varnames inclura des variables locales ainsi que des mots-clés. Cela n'aura probablement pas d'importance, car zip tronque la séquence plus courte, mais peut entraîner des messages d'erreur confus si vous passez le mauvais nombre d'arguments.

Vous pouvez éviter cela avec func_code.co_varnames[:func_code.co_argcount], mais mieux vaut utiliser le module inspect. à savoir:

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

Vous pouvez également gérer le cas où la fonction définit **kwargs ou *args (même si juste pour soulever une exception lorsqu'il est utilisé avec le décorateur). Si ceux-ci sont définis, le deuxième et le troisième résultat de getargspec renverront leur nom de variable, sinon ils seront None.

16

Tout argument passé en position sera transmis à * args. Et tout argument transmis en tant que mot clé sera transmis à ** kwargs. Si vous avez des valeurs args de position et les noms vous pouvez faire:

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

pour les convertir en args mot-clé.

+1

En fait, 'kwargs.update (zip (myfunc.func_code.co_varnames, args))' est suffisant. 'dict.update' gère aussi les itérations 2D. – obskyr

5

Eh bien, cela peut être exagéré. Je l'ai écrit pour le paquet dectools (sur PyPi), donc vous pouvez obtenir des mises à jour là-bas. Il retourne le dictionnaire en tenant compte des arguments positionnels, mots-clés et par défaut. Il y a une suite de test dans l'emballage (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

J'ai vu cela copié et collé dans des dizaines de projets open source. Cela devrait être déplacé vers une fonction que les gens peuvent appeler plus facilement! –

8

Si vous utilisez Python> = 2.7 inspect.getcallargs fait pour vous sortir de la boîte. Vous passeriez simplement la fonction décorée comme premier argument, puis le reste des arguments exactement comme vous l'appelez. Exemple:

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

Je prévois de faire f('p1', 'p2', 'p3', k2='k2', extra='kx1') (notez que k1 est passé positionnelle comme p3), alors ...

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

Si vous connaissez la fonction décorée n'utilisera **kwargs , alors cette clé n'apparaîtra pas dans le dict, et vous avez terminé (et je suppose qu'il n'y a pas *args, puisque cela casserait l'exigence que tout ait un nom).Si vous faites ont **kwargs, comme je l'ai dans cet exemple, et que vous souhaitez les inclure avec le reste des arguments nommés, il faut une ligne supplémentaire:

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

C'est la bonne façon de le faire (si vous avez Python 2.7 ou plus tard, ce que presque tout le monde fait). –

Questions connexes