Wie erhalte ich Methodenparameternamen?

235

Angesichts der Python-Funktion:

def a_method(arg1, arg2):
    pass

Wie kann ich die Anzahl und Namen der Argumente extrahieren? Dh, da ich einen Verweis auf habe func, möchte ich, dass der func.[something]zurückkehrt ("arg1", "arg2").

Das Verwendungsszenario hierfür ist, dass ich einen Dekorator habe und die Methodenargumente in derselben Reihenfolge verwenden möchte, in der sie für die eigentliche Funktion als Schlüssel angezeigt werden. Dh wie würde der Dekorateur so gedruckt aussehen, "a,b"wenn ich anrufe a_method("a", "b")?

Staale
quelle
1
Eine andere Liste von Antworten auf eine nahezu identische Frage finden Sie in diesem anderen Stackoverflow-Beitrag
Dan Mackinlay
1
Ihr Titel ist irreführend: Wenn man "Methode" für das Wort "Funktion" sagt, denkt man normalerweise an eine Klassenmethode. Für die Funktion ist Ihre ausgewählte Antwort (von Jouni K. Seppanen) gut. Aber für die (Klassen-) Methode funktioniert es nicht und die Inspektionslösung (von Brian) sollte verwendet werden.
Juh_

Antworten:

350

Schauen Sie sich das inspectModul an - dies führt die Überprüfung der verschiedenen Codeobjekteigenschaften für Sie durch.

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

Die anderen Ergebnisse sind der Name der Variablen * args und ** kwargs sowie die angegebenen Standardeinstellungen. dh.

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

Beachten Sie, dass einige Callables in bestimmten Implementierungen von Python möglicherweise nicht nachvollziehbar sind. In CPython stellen beispielsweise einige in C definierte integrierte Funktionen keine Metadaten zu ihren Argumenten bereit. Als Ergebnis erhalten Sie eine, ValueErrorwenn Sie inspect.getfullargspec()eine integrierte Funktion verwenden.

Seit Python 3.3 können Sie die inspect.signature()Aufrufsignatur eines aufrufbaren Objekts anzeigen:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Brian
quelle
29
Wie kann der Code möglicherweise wissen, dass der Standardparameter speziell (4,)dem Schlüsselwortparameter entspricht c?
Fatuhoku
63
@ Fatuhoku Ich habe mich das gleiche gefragt. Es stellt sich heraus, dass es nicht mehrdeutig ist, da Sie Standardargumente nur am Ende eines zusammenhängenden Blocks hinzufügen können. Aus den Dokumenten: "Wenn dieses Tupel n Elemente hat, entsprechen sie den letzten n Elementen, die in args aufgelistet sind"
Soverman
9
Ich denke, seit Python 3.x getargspec (...) durch inspector.signature (func) ersetzt wird
Diego Andrés Díaz Espinoza
2
In Version 2.6 geändert: Gibt ein benanntes Tupel ArgSpec zurück (Argumente, Variablen, Schlüsselwörter, Standardeinstellungen).
Ansager
4
Das ist richtig, @ DiegoAndrésDíazEspinoza - in Python 3 inspect.getargspecist veraltet , aber der Ersatz ist inspect.getfullargspec.
j08lue
100

In CPython beträgt die Anzahl der Argumente

a_method.func_code.co_argcount

und ihre Namen stehen am Anfang von

a_method.func_code.co_varnames

Dies sind Implementierungsdetails von CPython, daher funktioniert dies wahrscheinlich nicht in anderen Implementierungen von Python, wie z. B. IronPython und Jython.

Eine tragbare Möglichkeit, "Pass-Through" -Argumente zuzulassen, besteht darin, Ihre Funktion mit der Signatur zu definieren func(*args, **kwargs). Dies wird häufig in matplotlib verwendet , wo die äußere API-Schicht viele Schlüsselwortargumente an die untergeordnete API übergibt.

Jouni K. Seppänen
quelle
co_varnames funktioniert mit Standard-Python, diese Methode ist jedoch nicht vorzuziehen, da auch die internen Argumente angezeigt werden.
MattK
11
Warum nicht aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount] verwenden?
hochl
Nicht mit Argumenten arbeiten nach *args, zB:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin
@ Nikolay siehe stackoverflow.com/questions/147816/…
Brian McCutchon
Bitte verwenden Sie stattdessen inspect. Andernfalls funktioniert Ihr Code mit functools.wraps in 3.4+ nicht gut. Siehe stackoverflow.com/questions/147816/…
Brian McCutchon
22

In einer Dekorationsmethode können Sie Argumente der ursprünglichen Methode folgendermaßen auflisten:

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)

Wenn die **kwargsfür Sie wichtig sind, wird es etwas kompliziert:

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

Beispiel:

@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
quelle
Diese Antwort ist teilweise veraltet und sollte aktualisiert werden.
Imago
15

Ich denke, was Sie suchen, ist die Methode der Einheimischen -


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

In [7]: test(1,2)              
{'a': 1, 'b': 2}
Damian
quelle
7
Dies ist außerhalb einer Funktion nutzlos, die hier im Kontext von Interesse ist (Dekorateur).
Piotr Dobrogost
8
Eigentlich genau das, wonach ich gesucht habe, obwohl es hier nicht die Antwort auf die Frage ist.
Javabeangrinder
15

Die Python 3-Version ist:

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

Die Methode gibt ein Wörterbuch zurück, das sowohl args als auch kwargs enthält.

argaen
quelle
Beachten Sie, dass dies [:fn.__code__.co_argcount]sehr wichtig ist, wenn Sie nach Funktionsargumenten suchen. Andernfalls enthält es auch Namen, die innerhalb der Funktion erstellt wurden.
Soren Bjornstad
13

Hier ist etwas, von dem ich denke, dass es mit einem Dekorateur für das funktioniert, was Sie wollen.

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

Führen Sie es aus, es wird die folgende Ausgabe erhalten:

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
quelle
11

Python 3.5+:

DeprecationWarning: inspect.getargspec () ist veraltet. Verwenden Sie stattdessen inspect.signature ()

Also vorher:

func_args = inspect.getargspec(function).args

Jetzt:

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

Zu testen:

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

Da wir die Funktion 'function' haben, die das Argument 'arg' annimmt, wird dies als True bewertet, andernfalls als False.

Beispiel aus der Python-Konsole:

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

In Python 3. + mit dem Signaturevorliegenden Objekt können Sie mithilfe der Signaturmethode auf einfache Weise eine Zuordnung zwischen Argumentnamen und Werten bind()ermitteln.

Hier ist zum Beispiel ein Dekorateur zum Drucken einer Karte wie folgt:

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
quelle
6

Hier ist eine andere Möglichkeit, die Funktionsparameter ohne Verwendung eines Moduls abzurufen.

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

Ausgabe:

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

Gibt eine Liste mit Argumentnamen zurück, kümmert sich um Teilfunktionen und reguläre Funktionen:

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

Update für Brians Antwort :

Wenn eine Funktion in Python 3 nur Schlüsselwortargumente enthält, müssen Sie Folgendes verwenden inspect.getfullargspec:

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

ergibt dies:

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

In Python 3 ist unten zu machen *argsund **kwargszu einem dict(verwenden Sie OrderedDictfür Python <3.6, um dictOrdnungen zu pflegen ):

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
quelle
1

inspect.signatureist sehr langsam. Der schnellste Weg ist

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
quelle
0

Um Brians Antwort ein wenig zu aktualisieren , gibt es jetzt einen schönen Backport inspect.signature, den Sie in älteren Python-Versionen verwenden können : funcsigs. Also würde meine persönliche Präferenz gehen

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

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Wenn Sie zum Spaß mit SignatureObjekten spielen und sogar Funktionen mit zufälligen Signaturen dynamisch erstellen möchten, können Sie sich mein makefunProjekt ansehen .

smarie
quelle
-3

Was ist mit dir()und vars()jetzt?

Scheint genau das zu tun, was super einfach gefragt wird ...

Muss aus dem Funktionsumfang heraus aufgerufen werden.

Aber seien Sie vorsichtig, dass es alles zurückgeben wird lokalen Variablen zurückgegeben werden. Führen Sie dies bei Bedarf am Anfang der Funktion aus.

Beachten Sie auch, dass dies, wie in den Kommentaren ausgeführt, nicht von außerhalb des Bereichs möglich ist. Also nicht genau das OP-Szenario, passt aber trotzdem zum Fragentitel. Daher meine Antwort.

jeromej
quelle
dir () gibt eine Liste aller Variablennamen ['var1', 'var2'] zurück, vars () gibt ein Wörterbuch in der Form {'var1': 0, 'var2': 'etwas'} aus dem aktuellen lokalen Bereich zurück. Wenn jemand später in der Funktion Argumentvariablennamen verwenden möchte, sollte er in einer anderen lokalen Variablen speichern, da ein späterer Aufruf in der Funktion, in der er andere lokale Variablen deklarieren könnte, diese Liste "kontaminiert". Wenn sie es außerhalb der Funktion verwenden möchten, müssen sie die Funktion mindestens einmal ausführen und in einer globalen Variablen speichern. Daher ist es besser, das Inspect-Modul zu verwenden.
Peter Majko