Получение имен параметров метода в Python

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

def aMethod(arg1, arg2):
    pass

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

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

20 ответов

Решение

Взгляните на модуль inspect - он проведет проверку различных свойств объекта кода за вас.

>>> inspect.getfullargspec(aMethod)
(['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)>

В CPython количество аргументов

aMethod.func_code.co_argcount

и их имена в начале

aMethod.func_code.co_varnames

Это детали реализации CPython, так что это, вероятно, не работает в других реализациях Python, таких как IronPython и Jython.

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

Версия 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.

В методе декоратора вы можете перечислить аргументы исходного метода следующим образом:

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]

Я думаю, что вы ищете метод местных жителей -


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

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

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

Вот то, что я думаю, будет работать для того, что вы хотите, используя декоратор.

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.

В Python 3.+ с 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}

Вот еще один способ получить параметры функции без использования какого-либо модуля.

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

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

У меня есть еще одно предложение для тех, кто, как и я, ищет решение, которое помещает в декоратор все параметры и их значения (по умолчанию или нет) в словарь.

      import inspect

def get_arguments(func, args, kwargs, is_method=False):
    offset = 1 if is_method else 0
    specs = inspect.getfullargspec(func)
    d = {}
    for i, parameter in enumerate(specs.args[offset:]):
        i += offset
        if i < len(args):
            d[parameter] = args[i]
        elif parameter in kwargs:
            d[parameter] = kwargs[parameter]
        else:
            d[parameter] = specs.defaults[i - len(args)]
    return d

Теперь печатаем возвращаемое значение get_arguments внутри декоратора, подобного этому.

      def a_function_decorator(func):
    def inner(*args, **kwargs):
        print(get_arguments(func, args, kwargs))
        return func(*args, **kwargs)

    return inner

и примените его к такой функции, как

      @a_function_decorator
def foo(a, b, c="default_c", d="default_d"):
    pass

даст нам

      foo(1, 2, d="eek")
# {'a': 1, 'b': 2, 'c': 'default_c', 'd': 'eek'}

foo(1, 2, "blah")
# {'a': 1, 'b': 2, 'c': 'blah', 'd': 'default_c'}

foo(1, 2)
# {'a': 1, 'b': 2, 'c': 'default_c', 'd': 'default_d'}

То же самое работает для методов

      def a_method_decorator(func):
    def inner(*args, **kwargs):
        print(get_arguments(func, args, kwargs, is_method=True))
        return func(*args, **kwargs)

    return inner

class Bar:
    @a_method_decorator
    def foo(self, a, b, c="default_c", d="default_d"):
        pass

Bar().foo(1, 2, d="eek")
#{'a': 1, 'b': 2, 'c': 'default_c', 'd': 'eek'}
Bar().foo(1, 2, "blah")
# {'a': 1, 'b': 2, 'c': 'blah', 'd': 'default_c'}
Bar().foo(1, 2)
# {'a': 1, 'b': 2, 'c': 'default_c', 'd': 'default_d'}

Это, конечно, не самое красивое решение, но это первое, что я видел, которое делает именно то, что я хочу.

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

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

В питоне 3 ниже, чтобы сделать *args а также **kwargs в dict (использование OrderedDict для питона < 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

Обновление для ответа Брайана:

Если функция в 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={})

Простой легко читаемый ответ, начиная с Python 3.0 и выше:

      import inspect


args_names = inspect.signature(function).parameters.keys()
args_dict = {
    **dict(zip(args_names, args)),
    **kwargs,
}


Самый простой способ манипулировать именами параметров некоторой функции:

      parameters_list = list(inspect.signature(self.YOUR_FUNCTION).parameters))

Результат:

      ['YOUR_FUNCTION_parameter_name_0', 'YOUR_FUNCTION_parameter_name_1', ...]

Проделать этот путь будет еще проще, так как вы получите конкретный:

      parameters_list = list(inspect.signature(self.YOUR_FUNCTION).parameters)[0]

Результат:

      'YOUR_FUNCTION_parameter_name_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 проект.

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

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

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

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

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

Можно ли использовать inspect API для чтения постоянного значения аргумента -1 из лямбда-функции fun в коде ниже?

      def my_func(v, axis):
  pass

fun = lambda v: my_func(v, axis=-1)

Я искал в Google, как напечатать имя функции и предоставленные аргументы для назначения, мне пришлось создать декоратор для их печати, и я использовал это:

      def print_func_name_and_args(func):
    
    def wrapper(*args, **kwargs):
    print(f"Function name: '{func.__name__}' supplied args: '{args}'")
    func(args[0], args[1], args[2])
    return wrapper


@print_func_name_and_args
def my_function(n1, n2, n3):
    print(n1 * n2 * n3)
    
my_function(1, 2, 3)

#Function name: 'my_function' supplied args: '(1, 2, 3)'
Другие вопросы по тегам