Как я могу рассматривать позиционные аргументы как аргументы ключевого слова в Python 2

Для декоратора, который я пишу, я хотел бы манипулировать определенным именованным параметром функции. Рассмотрим следующий декоратор:

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
    return func_caller
return func_decorator

Применяется к следующей функции:

@square_param('dividend')
def quotient(divisor=1,dividend=0):
    return dividend/divisor

Это будет работать, если дивиденд вызывается как аргумент ключевого слова, например:

>>> quotient(dividend=2)
4

Однако, если он задан в качестве позиционного аргумента, это не сработает.

>>> quotient(3,4)
TypeError: quotient() got multiple values for keyword argument 'dividend'

В Python 3 я мог решить эту проблему, заставив параметр всегда указываться как ключевое слово:

@square_param('dividend')
def quotient(divisor=1,*,dividend=0):
    return dividend/divisor

но я хотел бы поддерживать Python 2, а также хотел бы наложить как можно меньше ограничений на функцию.

Есть ли способ исправить это поведение в моем декораторе?


person Peter Smit    schedule 05.07.2011    source источник
comment
Осмотрите модуль проверки. Там может быть то, что вам нужно для этого. Посмотрите на getargpec.   -  person Dirk    schedule 05.07.2011


Ответы (3)


Во-первых, ваш декоратор square_param не работает, потому что он не возвращает функции. Это должно быть:

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
        return func_caller
    return func_decorator

Теперь я последовал совету @Dirk и заглянул в модуль inspect. Вы можете сделать это, сначала проверив, является ли параметр одним из позиционных аргументов функции, а затем, если этот позиционный аргумент был указан, а затем изменив позицию этого аргумента. Вам также необходимо убедиться, что вы изменяете kwargs только в том случае, если параметр был предоставлен в качестве аргумента ключевого слова.

import inspect

def square_param(param):
    def func_decorator(func):
        def func_caller(*args,**kwargs):
            funparams = inspect.getargspec(func).args
            if param in funparams:
                i = funparams.index(param)
                if len(args) > i:
                    args = list(args)   # Make mutable
                    args[i] = args[i] * args[i]
            if param in kwargs:
                kwargs[param] = kwargs[param] * kwargs[param]
            return func(*args,**kwargs)
        return func_caller
    return func_decorator
person mgiuca    schedule 05.07.2011
comment
Спасибо, это именно то, что я искал! Что касается возврата функций, это действительно была маленькая глупая ошибка, допущенная в этом примере. - person Peter Smit; 05.07.2011

даже без использования Inspect мы можем получить параметры функции

>>> func = lambda x, y, args: (x, y, {})
>>> func.func_code.co_argcount
3
>>> func.func_code.co_varnames
('x', 'y', 'args')
person Yajushi    schedule 05.07.2011
comment
Спасибо! Не могли бы вы добавить ссылку, где задокументированы члены func_code? Я не могу найти это. - person Peter Smit; 05.07.2011
comment
Пожалуйста! func_code — это объект кода, представляющий тело скомпилированной функции. Дополнительные сведения см. в разделе «Вызываемые типы» по ссылке. - person Yajushi; 05.07.2011

Это может быть только косвенно связано, но я нашел полезным решить аналогичную проблему. Я хотел объединить *args и **kwargs в один словарь, чтобы мой следующий код мог обрабатываться независимо от того, как поступили аргументы, и я не хотел изменять существующую переменную kwargs, иначе я бы просто использовал kwargs.update().

all_args = {**kwargs, **{k: v for k, v in zip(list(inspect.signature(func).parameters), args)}}

# optionally delete `self`
del (all_args['self'])

Обновление: хотя это работает, этот ответ имеет лучшую технику. Частично:

bound_args = inspect.signature(f).bind(*args, **kwargs)
bound_args.apply_defaults()
person cjbarth    schedule 04.12.2019