Comment obtenir les noms des paramètres de méthode?

236

Étant donné la fonction Python:

def a_method(arg1, arg2):
    pass

Comment puis-je extraire le nombre et les noms des arguments. C'est-à-dire, étant donné que j'ai une référence à func, je veux que le func.[something]retour ("arg1", "arg2").

Le scénario d'utilisation pour cela est que j'ai un décorateur, et je souhaite utiliser les arguments de méthode dans le même ordre qu'ils apparaissent pour la fonction réelle comme clé. C'est-à-dire, à quoi ressemblerait le décorateur imprimé "a,b"lors de mon appel a_method("a", "b")?

Staale
la source
1
Pour une liste différente de réponses à une question presque identique, consultez cet autre post stackoverflow
dan mackinlay
1
Votre titre est trompeur: quand on dit «méthode» par le mot «fonction», on pense généralement à une méthode de classe. Pour la fonction, la réponse que vous avez choisie (de Jouni K. Seppanen) est bonne. Mais pour la méthode (class), cela ne fonctionne pas et la solution d'inspection (de Brian) doit être utilisée.
Juh_

Réponses:

352

Jetez un oeil au inspectmodule - cela fera l'inspection des différentes propriétés des objets de code pour vous.

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

Les autres résultats sont le nom des variables * args et ** kwargs et les valeurs par défaut fournies. c'est à dire.

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

Notez que certains callables peuvent ne pas être introspectables dans certaines implémentations de Python. Par exemple, dans CPython, certaines fonctions intégrées définies en C ne fournissent aucune métadonnée sur leurs arguments. En conséquence, vous obtiendrez un ValueErrorsi vous utilisez inspect.getfullargspec()une fonction intégrée.

Depuis Python 3.3, vous pouvez utiliser inspect.signature()pour voir la signature d'appel d'un objet appelable:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Brian
la source
29
Comment le code pourrait-il savoir que le paramètre par défaut (4,)correspond cspécifiquement au paramètre de mot-clé ?
fatuhoku
63
@fatuhoku Je me demandais la même chose. Il s'avère que ce n'est pas ambigu, car vous ne pouvez ajouter des arguments par défaut qu'à la fin dans un bloc contigu. Extrait de la documentation: "si ce tuple a n éléments, ils correspondent aux n derniers éléments répertoriés dans les
arguments
9
Je pense que depuis Python 3.x getargspec (...) est remplacé par inspector.signature (func)
Diego Andrés Díaz Espinoza
2
Modifié dans la version 2.6: renvoie un tuple nommé ArgSpec (args, varargs, mots-clés, valeurs par défaut).
theeannouncer
4
C'est vrai, @ DiegoAndrésDíazEspinoza - en Python 3, inspect.getargspecest obsolète , mais le remplacement l'est inspect.getfullargspec.
j08lue
100

En CPython, le nombre d'arguments est

a_method.func_code.co_argcount

et leurs noms sont au début de

a_method.func_code.co_varnames

Ce sont des détails d'implémentation de CPython, donc cela ne fonctionne probablement pas dans d'autres implémentations de Python, comme IronPython et Jython.

Une façon portable d'admettre des arguments "pass-through" consiste à définir votre fonction avec la signature func(*args, **kwargs). Ceci est beaucoup utilisé par exemple dans matplotlib , où la couche API externe transmet de nombreux arguments de mots clés à l'API de niveau inférieur.

Jouni K. Seppänen
la source
co_varnames fonctionne avec Python standard, mais cette méthode n'est pas préférable car elle affichera également les arguments internes.
MattK
11
Pourquoi ne pas utiliser aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl
Ne pas travailler avec des arguments après *args, par exemple:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin
@Nikolay voir stackoverflow.com/questions/147816/…
Brian McCutchon
Veuillez utiliser inspect à la place. Sinon, votre code ne fonctionne pas bien avec functools.wraps en 3.4+. Voir stackoverflow.com/questions/147816/…
Brian McCutchon
22

Dans une méthode décoratrice, vous pouvez répertorier les arguments de la méthode d'origine de cette façon:

import inspect, itertools 

def my_decorator():

        def decorator(f):

            def wrapper(*args, **kwargs):

                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

Si ce **kwargssont importants pour vous, alors ce sera un peu compliqué:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

Exemple:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]
Mehdi Behrooz
la source
Cette réponse est partiellement obsolète et doit être mise à jour.
Imago
15

Je pense que ce que vous cherchez, c'est la méthode des locaux -


In [6]: def test(a, b):print locals()
   ...: 

In [7]: test(1,2)              
{'a': 1, 'b': 2}
Damian
la source
7
Cela est inutile en dehors d'une fonction qui est le contexte qui nous intéresse ici (décorateur).
Piotr Dobrogost
8
En fait exactement ce que je cherchais bien que ce ne soit pas la réponse à la question ici.
javabeangrinder du
15

La version Python 3 est:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

La méthode renvoie un dictionnaire contenant à la fois args et kwargs.

argaen
la source
Notez que le [:fn.__code__.co_argcount]est très important si vous recherchez les arguments de la fonction - sinon il inclut également les noms créés dans la fonction.
Soren Bjornstad
13

Voici quelque chose qui, je pense, fonctionnera pour ce que vous voulez, en utilisant un décorateur.

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

Exécutez-le, il produira la sortie suivante:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.
hlzr
la source
11

Python 3.5+:

DeprecationWarning: inspect.getargspec () est déconseillé, utilisez plutôt inspect.signature ()

Alors auparavant:

func_args = inspect.getargspec(function).args

Maintenant:

func_args = list(inspect.signature(function).parameters.keys())

Tester:

'arg' in list(inspect.signature(function).parameters.keys())

Étant donné que nous avons la fonction «fonction» qui prend l'argument «arg», cela sera évalué comme Vrai, sinon comme Faux.

Exemple de la console Python:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True
Peter Majko
la source
8

En Python 3. + avec l' Signatureobjet à portée de main, un moyen facile d'obtenir un mappage entre les noms d'argument et les valeurs, utilise la bind()méthode de Signature !

Par exemple, voici un décorateur pour imprimer une carte comme ça:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}
Kfir Eisner
la source
6

Voici une autre façon d'obtenir les paramètres de la fonction sans utiliser de module.

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {}
    for i in sorted(kwargs.keys(), key=sorter.get):
        sorted_kwargs[i] = kwargs[i]      
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a


print(get_parameters(f))

Production:

(('a', 'b'), {'c': 'hello', 'd': 'world'})
dildeolupbiten
la source
2

Renvoie une liste de noms d'arguments, prend en charge les fonctions partielles et régulières:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)
amour
la source
2

Mise à jour pour la réponse de Brian :

Si une fonction en Python 3 a des arguments de mots clés uniquement, vous devez utiliser inspect.getfullargspec:

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

donne ceci:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})
ASMik09
la source
2

En python 3, ci-dessous est de faire *argset **kwargsen un dict(utiliser OrderedDictpour python <3.6 pour maintenir les dictcommandes):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper
Alpha
la source
1

inspect.signatureEst très lent. Le moyen le plus rapide est

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')
Nikolay Makhalin
la source
0

Pour mettre à jour un peu la réponse de Brian , il y a maintenant une belle rétroportage inspect.signatureque vous pouvez utiliser dans les versions de python âgées: funcsigs. Donc, ma préférence personnelle irait pour

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Pour le plaisir, si vous êtes intéressé à jouer avec des Signatureobjets et même à créer des fonctions avec des signatures aléatoires de manière dynamique, vous pouvez jeter un œil à mon makefunprojet.

smarie
la source
-3

Qu'en est- il dir()et vars()maintenant?

Semble faire exactement ce qui est demandé super simplement ...

Doit être appelé depuis la portée de la fonction.

Mais attention, il renverra toutes les variables locales, alors assurez-vous de le faire au tout début de la fonction si nécessaire.

Notez également que, comme indiqué dans les commentaires, cela ne permet pas de le faire depuis l'extérieur du champ d'application. Donc pas exactement le scénario d'OP mais correspond toujours au titre de la question. D'où ma réponse.

jeromej
la source
dir () renvoie la liste de tous les noms de variables ['var1', 'var2'], vars () renvoie le dictionnaire sous la forme {'var1': 0, 'var2': 'quelque chose'} à partir de la portée locale actuelle. Si quelqu'un veut utiliser des noms de variables d'argument plus tard dans la fonction, il doit enregistrer dans une autre variable locale, car l'appeler plus tard dans la fonction où il pourrait déclarer une autre variable locale "contamine" cette liste. Dans le cas où ils veulent l'utiliser en dehors de la fonction, ils doivent exécuter la fonction au moins une fois et l'enregistrer dans une variable globale. Il est donc préférable d'utiliser le module d'inspection.
Peter Majko