Jak uzyskać nazwy parametrów metody?

236

Biorąc pod uwagę funkcję Python:

def a_method(arg1, arg2):
    pass

Jak mogę wyodrębnić liczbę i nazwy argumentów. To znaczy, biorąc pod uwagę, że mam odniesienie func, chcę func.[something]wrócić ("arg1", "arg2").

Scenariusz użycia jest taki, że mam dekorator i chcę użyć argumentów metody w tej samej kolejności, w jakiej pojawiają się dla rzeczywistej funkcji jako klucza. To znaczy, jak wyglądałby dekorator wydrukowany, "a,b"kiedy dzwonię a_method("a", "b")?

Staale
źródło
1
Aby uzyskać inną listę odpowiedzi na prawie identyczne pytanie, zobacz ten inny post stackoverflow
dan mackinlay
1
Twój tytuł wprowadza w błąd: kiedy ktoś mówi „metoda”, pisze słowo „funkcja”, zwykle myśli się o metodzie klasy. Jeśli chodzi o funkcję, wybrana odpowiedź (od Jouni K. Seppanen) jest dobra. Ale w przypadku metody (klasy) nie działa ona i należy użyć rozwiązania inspekcji (od Briana).
Juh_

Odpowiedzi:

351

Spójrz na inspectmoduł - przeprowadzi to kontrolę różnych właściwości obiektu kodu.

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

Pozostałe wyniki to nazwa zmiennych * args i ** kwargs oraz podane wartości domyślne. to znaczy.

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

Zauważ, że niektóre wywołania mogą nie być introspektywne w niektórych implementacjach Pythona. Na przykład w CPython niektóre wbudowane funkcje zdefiniowane w C nie dostarczają metadanych dotyczących ich argumentów. W rezultacie otrzymasz, ValueErrorjeśli użyjesz inspect.getfullargspec()wbudowanej funkcji.

Od wersji Python 3.3 możesz używać inspect.signature()podpisu wywołania obiektu na żądanie:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Brian
źródło
29
Skąd kod może wiedzieć, że parametr domyślny (4,)odpowiada ckonkretnie parametrowi słowa kluczowego ?
fatuhoku
63
@fatuhoku Zastanawiałem się nad tym samym. Okazuje się, że nie jest to niejednoznaczne, ponieważ na końcu można dodawać tylko domyślne argumenty w ciągłym bloku. Z dokumentacji: „jeśli ta krotka ma n elementów, odpowiadają one ostatnim n elementom wymienionym w args”
Soverman
9
Myślę, że odkąd Python 3.x getargspec (...) został zastąpiony przez inspector.signature (func)
Diego Andrés Díaz Espinoza
2
Zmieniono w wersji 2.6: Zwraca nazwaną krotkę ArgSpec (args, varargs, słowa kluczowe, wartości domyślne).
prowadzący
4
Zgadza się, @ DiegoAndrésDíazEspinoza - w Pythonie 3 inspect.getargspecjest przestarzały , ale zastępuje inspect.getfullargspec.
j08lue
100

W CPython liczba argumentów wynosi

a_method.func_code.co_argcount

a ich imiona są na początku

a_method.func_code.co_varnames

Są to szczegóły implementacji CPython, więc prawdopodobnie nie działa to w innych implementacjach Pythona, takich jak IronPython i Jython.

Jednym przenośnym sposobem na dopuszczenie argumentów „pass-through” jest zdefiniowanie funkcji za pomocą podpisu func(*args, **kwargs). Jest to często używane np. W matplotlib , gdzie zewnętrzna warstwa API przekazuje wiele argumentów słów kluczowych do API niższego poziomu.

Jouni K. Seppänen
źródło
co_varnames działa ze standardowym Pythonem, ale ta metoda nie jest preferowana, ponieważ wyświetla również wewnętrzne argumenty.
MattK
11
Dlaczego nie skorzystać z aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl
Nie działa z argumentami po *args, np .:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin
@Nikolay patrz stackoverflow.com/questions/147816/...
Brian McCutchon
Zamiast tego użyj inspekcji. W przeciwnym razie kod nie będzie działał dobrze z funkools.wraps w wersji 3.4+. Zobacz stackoverflow.com/questions/147816/...
Brian McCutchon,
22

W metodzie dekoratora możesz wyświetlić listę argumentów oryginalnej metody w następujący sposób:

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)

Jeśli **kwargssą dla Ciebie ważne, będzie to trochę skomplikowane:

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

Przykład:

@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
źródło
Ta odpowiedź jest częściowo nieaktualna i powinna zostać zaktualizowana.
Imago
15

Myślę, że szukasz metody lokalnej -


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

In [7]: test(1,2)              
{'a': 1, 'b': 2}
Damian
źródło
7
Jest to bezużyteczne poza funkcją, która jest kontekstem zainteresowań tutaj (dekorator).
Piotr Dobrogost
8
Właściwie dokładnie tego szukałem, chociaż nie jest to odpowiedź na pytanie tutaj.
javabeangrinder
15

Wersja Python 3 to:

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

Metoda zwraca słownik zawierający zarówno argumenty, jak i kwargs.

argaen
źródło
Zauważ, że [:fn.__code__.co_argcount]jest to bardzo ważne, jeśli szukasz argumentów funkcji - w przeciwnym razie obejmuje to również nazwy utworzone w funkcji.
Soren Bjornstad
13

Oto coś, co myślę, że będzie działać na to, czego chcesz, używając dekoratora.

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

Uruchom go, da następujące wyniki:

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
źródło
11

Python 3.5+:

DeprecationWarning: inspect.getargspec () jest przestarzały, zamiast tego użyj inspect.signature ()

Więc wcześniej:

func_args = inspect.getargspec(function).args

Teraz:

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

Testować:

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

Biorąc pod uwagę, że mamy funkcję „function”, która przyjmuje argument „arg”, będzie to oceniane jako True, w przeciwnym razie jako False.

Przykład z konsoli 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
źródło
8

W Pythonie 3. +, mając Signaturepod ręką obiekt, łatwym sposobem uzyskania mapowania między nazwami argumentów a wartościami jest użycie podpisubind() !

Na przykład tutaj jest dekorator do drukowania takiej mapy:

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
źródło
6

Oto inny sposób uzyskania parametrów funkcji bez użycia żadnego modułu.

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

Wynik:

(('a', 'b'), {'c': 'hello', 'd': 'world'})
dildeolupbiten
źródło
2

Zwraca listę nazw argumentów, dba o częściowe i regularne funkcje:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)
lovesh
źródło
2

Aktualizacja odpowiedzi Briana :

Jeśli funkcja w Pythonie 3 zawiera argumenty zawierające tylko słowa kluczowe, musisz użyć inspect.getfullargspec:

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

daje to:

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

W Pythonie 3 poniżej znajduje się polecenie make *argsi **kwargsinto dict(użyj OrderedDictdla Pythona <3.6 do utrzymania dictzamówień):

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
Alfa
źródło
1

inspect.signaturejest bardzo wolny. Najszybszy sposób to

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
źródło
0

Aby zaktualizować trochę odpowiedź Briana , nie ma teraz ładne backport z inspect.signaturektórych można korzystać w starszych wersjach Pythona: funcsigs. Tak więc poszłyby moje osobiste preferencje

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

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Dla zabawy, jeśli chcesz grać z Signatureobiektami, a nawet dynamicznie tworzyć funkcje z losowymi podpisami, możesz rzucić okiem na mój makefunprojekt.

smarie
źródło
-3

Co teraz dir()i vars()teraz?

Wydaje się, że robienie dokładnie tego, o co proszono, po prostu…

Należy wywołać z zakresu funkcji.

Ale bądź ostrożny, że zwróci wszystkie zmienne lokalne, więc w razie potrzeby zrób to na samym początku funkcji.

Należy również zauważyć, że, jak wskazano w komentarzach, nie pozwala to zrobić spoza zakresu. Więc nie do końca scenariusz OP, ale nadal pasuje do tytułu pytania. Stąd moja odpowiedź.

jeromej
źródło
dir () zwraca listę wszystkich nazw zmiennych [„var1”, „var2”], vars () zwraca słownik w postaci {„var1”: 0, „var2”: „coś”} z bieżącego zakresu lokalnego. Jeśli ktoś chce użyć nazw zmiennych argumentów później w funkcji, powinien zapisać je w innej zmiennej lokalnej, ponieważ wywołanie jej później w funkcji, w której mogłyby zadeklarować inne zmienne lokalne „zanieczyści” tę listę. W przypadku, gdy chcą używać go poza funkcją, muszą uruchomić funkcję co najmniej raz i zapisać ją w zmiennej globalnej. Lepiej więc użyć modułu inspekcji.
Peter Majko