Як отримати назви параметрів методу?


236

З огляду на функцію Python:

def a_method(arg1, arg2):
    pass

Як я можу отримати число та назви аргументів. Тобто, враховуючи, що я маю посилання func, я хочу func.[something]повернутися ("arg1", "arg2").

Сценарій використання для цього полягає в тому, що у мене є декоратор, і я хочу використовувати аргументи методу в тому ж порядку, в якому вони відображаються для фактичної функції в якості ключа. Тобто, як би виглядав декоратор, який друкується, "a,b"коли я дзвоню a_method("a", "b")?


1
Щоб отримати інший список відповідей на майже ідентичне запитання, дивіться цей інший пост
stackoverflow

1
Ваш заголод вводить в оману: коли сказати "метод" записати слово "функція", зазвичай думають про метод класу. Для функціонування обрана відповідь (від Джоні К. Сеппанен). Але для (класового) методу він не працює, і слід використовувати рішення для перевірки (від Брайана).
Juh_

Відповіді:


351

Погляньте на inspectмодуль - це зробить для вас перевірку різних властивостей кодового об'єкта.

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

Інші результати - це назва змінних * args та ** kwargs та надані за замовчуванням. тобто.

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

Зауважте, що деякі виклики можуть не бути спроможними до певного впровадження Python. Наприклад, у CPython деякі вбудовані функції, визначені в C, не містять метаданих про свої аргументи. Як результат, ви отримаєте, ValueErrorякщо використовуватимете inspect.getfullargspec()вбудовану функцію.

Оскільки Python 3.3, ви можете використовувати inspect.signature()для перегляду підпису виклику об'єкта, що викликається:

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

29
Як код, можливо, знає, що параметр за замовчуванням (4,)відповідає cконкретно параметру ключового слова ?
fatuhoku

63
@fatuhoku Мені було цікаво те саме. Виявляється, це не неоднозначно, оскільки ви можете додавати аргументи за замовчуванням лише наприкінці у суміжний блок. З документів: "якщо цей кортеж має n елементів, вони відповідають останнім n елементам, переліченим у аргументах",
Soverman

9
Думаю, оскільки Python 3.x getargspec (...) замінений на inspector.signature (func)
Дієго Андрес Діас Еспіноза

2
Змінено у версії 2.6: повертає названий кортеж ArgSpec (arg, varargs, ключові слова, параметри за замовчуванням).
диктор

4
Тобто право, @ DiegoAndrésDíazEspinoza - в Python 3, inspect.getargspecє застарілим , але заміна inspect.getfullargspec.
j08lue

100

У CPython кількість аргументів є

a_method.func_code.co_argcount

і їх імена на початку

a_method.func_code.co_varnames

Це деталі реалізації CPython, тому це, ймовірно, не працює в інших реалізаціях Python, таких як IronPython та Jython.

Один з переносних способів визнати аргументи «прохідними» - це визначити свою функцію за допомогою підпису func(*args, **kwargs). Це багато використовується, наприклад, у matplotlib , де зовнішній шар API передає безліч аргументів ключових слів API нижчого рівня.


co_varnames працює зі стандартним Python, але цей метод не є кращим, оскільки він також відображатиме внутрішні аргументи.
MattK

11
Чому б не використовувати aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl

Не працюючи з аргументами після *args, наприклад:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Микола Махалін


Будь ласка, використовуйте перевірити замість цього. В іншому випадку ваш код не працює добре з functools.wraps в версії 3.4 або більше. Див stackoverflow.com/questions/147816 / ...
Брайан McCutchon

22

У методі декоратора ви можете перераховувати аргументи оригінального методу таким чином:

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)

Якщо **kwargsце важливо для вас, то це буде трохи складніше:

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

Приклад:

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

Ця відповідь частково застаріла і має бути оновлена.
Імаго

15

Я думаю, що ви шукаєте метод місцевих жителів -


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

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

7
Це марно поза функцією, яка є цікавим контекстом (декоратор).
Пьотр Доброгост

8
Насправді саме те, що я шукав, хоча це не відповідь на питання тут.
javabeangrinder

15

Версія 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}

Метод повертає словник, що містить і args, і kwargs.


Зауважте, що [:fn.__code__.co_argcount]це дуже важливо, якщо ви шукаєте аргументи функції - інакше він включає імена, створені в межах функції.
Сорен Бьорнстад

13

Ось те, що я думаю, буде працювати для того, що ви хочете, використовуючи декоратор.

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

Запустивши його, ви отримаєте такий вихід:

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 () застаріло, замість цього використовуйте inspect.signature ()

Отже, раніше:

func_args = inspect.getargspec(function).args

Зараз:

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

Перевіряти:

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

Зважаючи на те, що у нас є функція 'function', яка бере аргумент 'arg', це буде оцінюватися як True, інакше як False.

Приклад з консолі 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

У Python 3. + з Signatureоб'єктом під рукою, простий спосіб отримати відображення між іменами аргументів на значення, використовуючи bind()метод Signature !

Наприклад, ось декоратор для друку такої карти:

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

Ось ще один спосіб отримати параметри функції без використання жодного модуля.

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

Вихід:

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

2

Повертає список імен аргументів, дбає про партії та регулярні функції:

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

2

Оновлення відповіді Брайана :

Якщо функція в Python 3 має аргументи лише для ключових слів, вам потрібно використовувати inspect.getfullargspec:

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

дає це:

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

2

У python 3, нижче, потрібно зробити *argsта **kwargsперетворити dict(використовувати OrderedDictдля python <3.6 для підтримки dictзамовлень):

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дуже повільно. Найшвидший спосіб

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

Щоб оновити трохи відповідь Брайана , тепер є хороший перенести з inspect.signatureякий ви можете використовувати в більш старих версіях Python: funcsigs. Тому моє особисте уподобання піде на користь

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

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Для задоволення, якщо вам цікаво грати з Signatureоб’єктами і навіть динамічно створювати функції з випадковими підписами, ви можете подивитися на мій makefunпроект.


-3

Що про dir()і vars()тепер?

Здається, робити саме те, що просять супер просто ...

Потрібно викликати всередині функції.

Але будьте обережні, що вона поверне всі локальні змінні, тому обов'язково виконайте це на самому початку функції, якщо це необхідно.

Також зауважте, що, як зазначено в коментарях, це не дозволяє зробити це поза межами сфери застосування. Тож не зовсім сценарій роботи ОП, але все ще відповідає назві питання. Звідси моя відповідь.


dir () повертає список усіх імен змінних ['var1', 'var2'], vars () повертає словник у формі {'var1': 0, 'var2': 'something'} з поточної локальної області. Якщо хтось хоче використовувати імена змінних аргументів пізніше у функції, він повинен зберегти в іншій локальній змінній, тому що виклик її пізніше у функції, де вони могли б оголосити інші локальні змінні, "забруднить" цей список. У випадку, якщо вони хочуть використовувати його поза функцією, вони повинні запустити функцію хоча б один раз і зберегти її у глобальній змінній. Тож краще використовувати модуль перевірки.
Петро Майко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.