Декораторы вложенных функций, которые работают с аргументами в python - PullRequest
11 голосов
/ 10 октября 2011

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

from inspect import getargspec
from functools import wraps

def dec(id):
    def _dec(fn):
        @wraps(fn)
        def __dec(*args, **kwargs):
            if len(args):
                return fn(args[0], *args[1:], **kwargs)
            else:
                first_arg = getargspec(fn).args[0]
                new_kwargs = kwargs.copy()
                del new_kwargs[first_arg]
                return fn(kwargs[first_arg], **new_kwargs)
        return __dec
    return _dec

@dec(1)
def functionWithOneDecorator(a, b, c):
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)

@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)

functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)

functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)

Когда я запускаю приведенный выше код, я получаю следующий вывод:

functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithOneDecorator(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
functionWithTwoDecorators(a = 1, b = 2, c = 3)
IndexError: list index out of range

Это потому, чтокогда второй декоратор проверяет функцию, которую он декорирует, чтобы найти имена аргументов и терпит неудачу, потому что он декорирует декоратор, и он принимает только * args и ** kwargs.

Я могу придумать способы обойти проблему, котораяработать в приведенном выше коде, но все равно сломался бы, если бы функция была украшена моим декоратором и другой от третьей стороны.Есть ли общий способ исправить это?или есть лучший способ добиться того же результата?

Обновление: Спасибо @Hernan за указание на модуль декоратора .Это решает эту проблему точно.Теперь мой код выглядит так:

from decorator import decorator

def dec(id):
    @decorator
    def _dec(fn, *args, **kwargs):
        return fn(args[0], *args[1:], **kwargs)
    return _dec

@dec(1)
def functionWithOneDecorator(a, b, c):
    print "functionWithOneDecorator(a = %s, b = %s, c = %s)" % (a, b, c)

@dec(1)
@dec(2)
def functionWithTwoDecorators(a, b, c):
    print "functionWithTwoDecorators(a = %s, b = %s, c = %s)" % (a, b, c)

functionWithOneDecorator(1, 2, 3)
functionWithOneDecorator(1, b=2, c=3)
functionWithOneDecorator(a=1, b=2, c=3)
functionWithOneDecorator(c=3, b=2, a=1)

functionWithTwoDecorators(1, 2, 3)
functionWithTwoDecorators(1, b=2, c=3)
functionWithTwoDecorators(a=1, b=2, c=3)
functionWithTwoDecorators(c=3, b=2, a=1)    

намного чище, и работает!

1 Ответ

5 голосов
/ 10 октября 2011

Проблема в том, что подпись вашей оформленной функции не является подписью (getargspec) от оригинала.Это действительно хорошо объяснено с помощью модуля декоратора , который поможет решить вашу проблему.По сути, вы должны использовать сохраняющие подпись декораторы, чтобы второй декоратор видел ту же сигнатуру, что и первая.

...