Come ottenere i nomi dei parametri del metodo?


236

Data la funzione Python:

def a_method(arg1, arg2):
    pass

Come posso estrarre il numero e i nomi degli argomenti. Vale a dire, dato che ho un riferimento a func, voglio func.[something]che torni ("arg1", "arg2").

Lo scenario di utilizzo per questo è che ho un decoratore e desidero usare gli argomenti del metodo nello stesso ordine in cui appaiono per la funzione effettiva come chiave. Cioè, come apparirebbe il decoratore stampato "a,b"quando chiamo a_method("a", "b")?


1
Per un diverso elenco di risposte a una domanda quasi identica, vedi questo post di StackOverflow
dan mackinlay

1
Il tuo titolo è fuorviante: quando si dice 'metodo' ha scritto la parola 'funzione', di solito si pensa a un metodo di classe. Per quanto riguarda la funzione, la risposta selezionata (da Jouni K. Seppanen) è buona. Ma per il metodo (di classe), non funziona e dovrebbe essere usata la soluzione inspect (di Brian).
Giovedì

Risposte:


352

Dai un'occhiata al inspectmodulo - questo farà l'ispezione delle varie proprietà dell'oggetto codice per te.

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

Gli altri risultati sono il nome delle variabili * args e ** kwargs e le impostazioni predefinite fornite. vale a dire.

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

Si noti che alcuni callable potrebbero non essere irrintracciabili in alcune implementazioni di Python. Ad esempio, in CPython, alcune funzioni integrate definite in C non forniscono metadati sui loro argomenti. Di conseguenza, otterrai un ValueErrorse lo usi inspect.getfullargspec()su una funzione integrata.

Da Python 3.3, puoi usare inspect.signature()per vedere la firma della chiamata di un oggetto richiamabile:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

29
Come può il codice sapere eventualmente che il parametro predefinito (4,)corrisponde cspecificamente al parametro della parola chiave ?
fatuhoku,

63
@fatuhoku mi chiedevo la stessa cosa. Si scopre che non è ambiguo poiché alla fine è possibile aggiungere solo argomenti predefiniti in un blocco contiguo. Dai documenti: "se questa tupla ha n elementi, corrispondono agli ultimi n elementi elencati in args"
Soverman,

9
Penso che da quando Python 3.x getargspec (...) è sostituito da inspector.signature (func)
Diego Andrés Díaz Espinoza,

2
Modificato nella versione 2.6: restituisce una tupla ArgSpec denominata (args, varargs, parole chiave, valori predefiniti).
compleanno

4
Esatto, @ DiegoAndrésDíazEspinoza - in Python 3, inspect.getargspecè obsoleto , ma la sostituzione è inspect.getfullargspec.
j08lue,

100

In CPython, il numero di argomenti è

a_method.func_code.co_argcount

e i loro nomi sono all'inizio di

a_method.func_code.co_varnames

Questi sono i dettagli di implementazione di CPython, quindi probabilmente questo non funziona in altre implementazioni di Python, come IronPython e Jython.

Un modo portatile per ammettere argomenti "pass-through" è definire la tua funzione con la firma func(*args, **kwargs). Questo è molto usato, ad esempio , in matplotlib , dove il livello API esterno passa molti argomenti di parole chiave all'API di livello inferiore.


co_varnames funziona con Python standard, ma questo metodo non è preferibile poiché visualizzerà anche gli argomenti interni.
MattK,

11
Perché non usare aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
Hochl,

Non funziona con argomenti dopo *args, ad esempio:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin


Utilizzare invece inspect. Altrimenti, il tuo codice non funziona bene con functools.wraps in 3.4+. Vedere stackoverflow.com/questions/147816/...
Brian McCutchon

22

In un metodo decoratore, puoi elencare gli argomenti del metodo originale in questo modo:

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)

Se **kwargssono importanti per te, sarà un po 'complicato:

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

Esempio:

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

Questa risposta è parzialmente obsoleta e dovrebbe essere aggiornata.
Imago,

15

Penso che quello che stai cercando sia il metodo dei locali -


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

In [7]: test(1,2)              
{'a': 1, 'b': 2}

7
Questo è inutile al di fuori di una funzione che è il contesto di interesse qui (decoratore).
Piotr Dobrogost,

8
In realtà esattamente quello che stavo cercando, anche se non è la risposta alla domanda qui.
javabeangrinder,

15

La versione di Python 3 è:

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

Il metodo restituisce un dizionario contenente sia args che kwargs.


Nota che [:fn.__code__.co_argcount]è molto importante se stai cercando gli argomenti della funzione, altrimenti include anche i nomi creati all'interno della funzione.
Soren Bjornstad,

13

Ecco qualcosa che penso funzionerà per quello che vuoi, usando un decoratore.

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

Eseguilo, produrrà il seguente output:

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.

11

Python 3.5+:

DeprecationWarning: inspect.getargspec () è deprecato, utilizzare invece inspect.signature ()

Quindi in precedenza:

func_args = inspect.getargspec(function).args

Adesso:

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

Testare:

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

Dato che abbiamo la funzione 'funzione' che prende l'argomento 'arg', questo verrà valutato come Vero, altrimenti come Falso.

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

8

In Python 3. + con l' Signatureoggetto a portata di mano, un modo semplice per ottenere una mappatura tra i nomi degli argomenti e i valori è usare il bind()metodo Signature !

Ad esempio, ecco un decoratore per stampare una mappa del genere:

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}

6

Ecco un altro modo per ottenere i parametri della funzione senza usare alcun modulo.

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

Produzione:

(('a', 'b'), {'c': 'hello', 'd': 'world'})

2

Restituisce un elenco di nomi di argomenti, si occupa dei parziali e delle funzioni regolari:

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

2

Aggiornamento per la risposta di Brian :

Se una funzione in Python 3 ha argomenti di sola parola chiave, è necessario utilizzare inspect.getfullargspec:

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

produce questo:

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

2

In python 3, di seguito è necessario creare *argse **kwargsinserire un dict(utilizzare OrderedDictper python <3.6 per mantenere gli dictordini):

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

1

inspect.signatureè molto lento. Il modo più veloce è

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

0

Per aggiornare un po ' la risposta di Brian , v'è ora un bel backport di inspect.signatureche è possibile utilizzare nelle versioni più vecchie di Python: funcsigs. Quindi la mia preferenza personale andrebbe bene

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

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Per divertimento, se sei interessato a giocare con gli Signatureoggetti e persino a creare dinamicamente funzioni con firme casuali puoi dare un'occhiata al mio makefunprogetto.


-3

Che dire dir()e vars()adesso?

Sembra fare esattamente ciò che viene chiesto super semplicemente ...

Deve essere chiamato dall'ambito della funzione.

Ma fai attenzione che restituirà tutte le variabili locali, quindi assicurati di farlo all'inizio della funzione, se necessario.

Si noti inoltre che, come sottolineato nei commenti, ciò non consente di farlo al di fuori dell'ambito. Quindi non esattamente lo scenario di OP, ma corrisponde ancora al titolo della domanda. Da qui la mia risposta.


dir () restituisce l'elenco di tutti i nomi delle variabili ['var1', 'var2'], vars () restituisce il dizionario nella forma {'var1': 0, 'var2': 'qualcosa'} dall'ambito locale corrente. Se qualcuno desidera utilizzare i nomi delle variabili degli argomenti in un secondo momento nella funzione, dovrebbe salvarli in un'altra variabile locale, poiché chiamarlo in seguito nella funzione in cui potrebbero dichiarare un'altra variabile locale "contaminerà" questo elenco. Nel caso in cui desiderino utilizzarlo al di fuori della funzione, devono eseguire la funzione almeno una volta e salvarla nella variabile globale. Quindi è meglio usare il modulo inspect.
Peter Majko,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.