Pouvez-vous lister les arguments de mot-clé qu'une fonction reçoit?

104

J'ai un dict, dont j'ai besoin pour passer des clés / valeurs comme arguments de mot-clé. Par exemple ..

d_args = {'kw1': 'value1', 'kw2': 'value2'}
example(**d_args)

Cela fonctionne bien, mais s'il y a des valeurs dans le dict d_args qui ne sont pas acceptées par la examplefonction, il meurt évidemment. Disons, si la fonction d'exemple est définie commedef example(kw2):

C'est un problème car je ne contrôle ni la génération du d_args, ni la examplefonction .. Ils viennent tous les deux de modules externes, et examplen'acceptent que certains des mots-clés-arguments du dict ..

Idéalement, je ferais juste

parsed_kwargs = feedparser.parse(the_url)
valid_kwargs = get_valid_kwargs(parsed_kwargs, valid_for = PyRSS2Gen.RSS2)
PyRSS2Gen.RSS2(**valid_kwargs)

Je vais probablement filtrer simplement le dict, à partir d'une liste d'arguments-mots-clés valides, mais je me demandais: y a-t-il un moyen de lister par programme les arguments de mots-clés qu'une fonction spécifique prend?

dbr
la source

Réponses:

150

Un peu plus agréable que d'inspecter directement l'objet de code et de travailler sur les variables est d'utiliser le module inspect.

>>> import inspect
>>> def func(a,b,c=42, *args, **kwargs): pass
>>> inspect.getargspec(func)
(['a', 'b', 'c'], 'args', 'kwargs', (42,))

Si vous voulez savoir s'il est appelable avec un ensemble particulier d'arguments, vous avez besoin des args sans une valeur par défaut déjà spécifiée. Ceux-ci peuvent être obtenus par:

def getRequiredArgs(func):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if defaults:
        args = args[:-len(defaults)]
    return args   # *args and **kwargs are not required, so ignore them.

Ensuite, une fonction pour dire ce que vous manquez dans votre dict particulier est:

def missingArgs(func, argdict):
    return set(getRequiredArgs(func)).difference(argdict)

De même, pour vérifier les arguments non valides, utilisez:

def invalidArgs(func, argdict):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if varkw: return set()  # All accepted
    return set(argdict) - set(args)

Et donc un test complet s'il est appelable est:

def isCallableWithArgs(func, argdict):
    return not missingArgs(func, argdict) and not invalidArgs(func, argdict)

(Ceci n'est bon que pour l'analyse des arguments de python. Tout runtime vérifie les valeurs non valides dans kwargs ne peut évidemment pas être détecté.)

Brian
la source
Agréable! Je ne connaissais pas cette fonction!
DzinX
1
Étant donné que la méthode utilisant des objets de code est plus ou moins identique, y a-t-il un avantage à avoir dû importer un module supplémentaire ...?
jmetz
@jmets - définitivement - il est pratiquement toujours préférable d'utiliser un module de bibliothèque que de lancer le vôtre. De plus, les attributs de l'objet code sont plus internes et sont à modifier (par exemple, notez que cela a été déplacé vers le code dans pyhon3). L'utilisation du module comme interface vous protège un peu plus au cas où certains de ces éléments internes changeraient. Il fera également des choses que vous n'auriez peut-être pas pensé faire, comme lancer une erreur de type appropriée sur les fonctions que vous ne pouvez pas inspecter (par exemple, les fonctions C).
Brian
13
inspect.getargspec(f)est obsolète depuis Python 3.0; la méthode moderne est inspect.signature(f).
gerrit le
Pour info, si vous souhaitez prendre en charge Cython et Python, cette méthode ne fonctionne pas sur une fonction Cython. L' co_varnamesoption, en revanche, fonctionne dans les deux.
partie du
32

Cela affichera les noms de tous les arguments passables, mots-clés et non-mots-clés:

def func(one, two="value"):
    y = one, two
    return y
print func.func_code.co_varnames[:func.func_code.co_argcount]

C'est parce que les premiers co_varnamessont toujours des paramètres (ensuite les variables locales, comme ydans l'exemple ci-dessus).

Alors maintenant, vous pouvez avoir une fonction:

def getValidArgs(func, argsDict):
    '''Return dictionary without invalid function arguments.'''
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validArgs)

Que vous pourriez ensuite utiliser comme ceci:

>>> func(**getValidArgs(func, args))

EDIT : Un petit ajout: si vous n'avez vraiment besoin que des arguments de mots clés d'une fonction, vous pouvez utiliser l' func_defaultsattribut pour les extraire:

def getValidKwargs(func, argsDict):
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    kwargsLen = len(func.func_defaults) # number of keyword arguments
    validKwargs = validArgs[-kwargsLen:] # because kwargs are last
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validKwargs)

Vous pouvez maintenant appeler votre fonction avec des arguments connus, mais des kwargs extraits, par exemple:

func(param1, param2, **getValidKwargs(func, kwargsDict))

Cela suppose qu'il funcn'utilise aucun *argsou de la **kwargsmagie dans sa signature.

DzinX
la source
que faire si je veux imprimer uniquement les arguments "mots clés" "clés"?
Jia
7

Dans Python 3.0:

>>> import inspect
>>> import fileinput
>>> print(inspect.getfullargspec(fileinput.input))
FullArgSpec(args=['files', 'inplace', 'backup', 'bufsize', 'mode', 'openhook'],
varargs=None, varkw=None, defaults=(None, 0, '', 0, 'r', None), kwonlyargs=[], 
kwdefaults=None, annotations={})
jfs
la source
7

Pour une solution Python 3, vous pouvez utiliser inspect.signatureet filtrer en fonction du type de paramètres que vous souhaitez connaître.

Prenant un exemple de fonction avec des paramètres de position ou de mot-clé, de mot-clé uniquement, var positional et var mot-clé:

def spam(a, b=1, *args, c=2, **kwargs):
    print(a, b, args, c, kwargs)

Vous pouvez lui créer un objet de signature:

from inspect import signature
sig =  signature(spam)

puis filtrez avec une liste de compréhension pour trouver les détails dont vous avez besoin:

>>> # positional or keyword
>>> [p.name for p in sig.parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD]
['a', 'b']
>>> # keyword only
>>> [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]
['c']

et, de même, pour var positionals en utilisant p.VAR_POSITIONALet var mot-clé avec VAR_KEYWORD.

De plus, vous pouvez ajouter une clause au if pour vérifier si une valeur par défaut existe en vérifiant si p.defaultégal à p.empty.

Dimitris Fasarakis Hilliard
la source
3

Extension de la réponse de DzinX:

argnames = example.func_code.co_varnames[:func.func_code.co_argcount]
args = dict((key, val) for key,val in d_args.iteritems() if key in argnames)
example(**args)
Claudiu
la source