Как получить имена параметров метода?

236

Учитывая функцию Python:

def a_method(arg1, arg2):
    pass

Как я могу извлечь количество и имена аргументов. То есть, учитывая, что у меня есть ссылка наfunc , я хочу func.[something]вернуться ("arg1", "arg2").

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

Staale
источник
1
Для другого списка ответов на почти идентичный вопрос, см. Этот другой пост stackoverflow пост
Dan Mackinlay
1
Ваш заголовок вводит в заблуждение: когда кто-то говорит «метод» вместо слова «функция», он обычно думает о методе класса. Для функции, ваш выбранный ответ (от Джуни К. Сеппанен) хорош. Но для (класса) метод, он не работает, и следует использовать решение проверки (от Брайана).
Juh_

Ответы:

352

Посмотрите на 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?
фатухоку
63
@fatuhoku Мне было интересно то же самое. Оказывается, это не является двусмысленным, так как вы можете добавить аргументы по умолчанию только в конце в непрерывном блоке. Из документов: «если этот кортеж имеет n элементов, они соответствуют последним n элементам, перечисленным в аргументах»
Soverman
9
Я думаю, что с Python 3.x getargspec (...) заменяется инспектором. Подпись (func)
Диего Андрес Диас Эспиноза
2
Изменено в версии 2.6: Возвращает именованный кортеж ArgSpec (args, varargs, ключевые слова, значения по умолчанию).
анонс
4
То есть право, @ DiegoAndrésDíazEspinoza - в Python 3, inspect.getargspecявляется устаревшим , но замена inspect.getfullargspec.
J08Llue
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
Николай Махалин
@Nikolay см stackoverflow.com/questions/147816/...
Брайан McCutchon
Пожалуйста, используйте вместо этого проверить. В противном случае ваш код не будет хорошо работать с 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}
Damian
источник
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.

argaen
источник
Обратите внимание, что [: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.
hlzr
источник
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объектом под рукой, простой способ получить соответствие между именами аргументов и значениями, это использовать метод Signature bind()!

Например, вот декоратор для печати такой карты:

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'})
dildeolupbiten
источник
2

Возвращает список имен аргументов, заботится о частичках и обычных функциях:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)
lovesh
источник
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={})
ASMik09
источник
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проект.

smarie
источник
-3

Как насчет dir()иvars() сейчас?

Кажется, делает именно то, что просят супер просто ...

Должен вызываться из области действия функции.

Но будьте осторожны, что он вернет все локальные переменные, поэтому при необходимости обязательно сделайте это в самом начале функции.

Также обратите внимание, что, как указано в комментариях, это не позволяет делать это вне области видимости. Так что не совсем сценарий ОП, но все равно соответствует заголовку вопроса. Отсюда и мой ответ.

jeromej
источник
dir () возвращает список всех имен переменных ['var1', 'var2'], vars () возвращает словарь в форме {'var1': 0, 'var2': 'кое-что'} из текущей локальной области видимости. Если кто-то захочет использовать имена переменных-аргументов позже в функции, он должен сохранить их в другой локальной переменной, потому что вызов ее позже в функции, где он может объявить другие локальные переменные, «загрязнит» этот список. В случае, если они хотят использовать его вне функции, они должны запустить функцию хотя бы один раз и сохранить ее в глобальной переменной. Так что лучше использовать модуль проверки.
Питер Майко