Подтвердить что ты не робот

Python Class Based Decorator с параметрами, которые могут украсить метод или функцию

Я видел много примеров декораторов Python, которые:

  • декораторы стиля функции (обертывание функции)
  • декораторы стиля класса (реализация __init__, __get__ и __call__)
  • декораторы, которые не принимают аргументы
  • декораторы, которые принимают аргументы
  • декораторы, которые являются "дружественными к методу" (т.е. могут украсить метод в классе)
  • декораторы, которые являются "дружественными к функциям" (могут украшать простую функцию
  • декораторы, которые могут украшать как методы, так и функции

Но я никогда не видел ни одного примера, который мог бы сделать все вышеизложенное, и у меня возникли проблемы с синтезом из разных ответов на конкретные вопросы (например, этот, этот, или этот (который имеет один из лучших ответов, которые я когда-либо видел на SO)), как совместить все вышеперечисленное.

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

class MyDecorator(object):
    def __init__(self, fn, argument):
        self.fn = fn
        self.arg = argument

    def __get__(self, ....):
        # voodoo magic for handling distinction between method and function here

    def __call__(self, *args, *kwargs):
        print "In my decorator before call, with arg %s" % self.arg
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.arg


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"


@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

some_other_function()
Foo().bar()

И я ожидаю увидеть:

In my decorator before call, with arg some other func!
in some other function!
In my decorator after call, with arg some other func!
In my decorator before call, with arg foo baby!
in bar!
In my decorator after call, with arg foo baby!

Изменить: если это имеет значение, я использую Python 2.7.

4b9b3361

Ответ 1

Вам не нужно возиться с дескрипторами. Этого достаточно, чтобы создать оберточную функцию внутри метода __call__() и вернуть ее. Стандартные функции Python всегда могут действовать как метод или функция в зависимости от контекста:

class MyDecorator(object):
    def __init__(self, argument):
        self.arg = argument

    def __call__(self, fn):
        @functools.wraps(fn)
        def decorated(*args, **kwargs):
            print "In my decorator before call, with arg %s" % self.arg
            fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % self.arg
        return decorated

Немного о том, что происходит, когда этот декоратор используется следующим образом:

@MyDecorator("some other func!")
def some_other_function():
    print "in some other function!"

Первая строка создает экземпляр MyDecorator и передает "some other func!" в качестве аргумента __init__(). Позвольте называть этот экземпляр my_decorator. Далее, объект undecorated function - пусть его вызывает bare_func - создается и передается экземпляру декоратора, поэтому выполняется my_decorator(bare_func). Это вызовет MyDecorator.__call__(), который создаст и вернет функцию обертки. Наконец, этой функции оболочки присваивается имя some_other_function.

Ответ 2

Вам не хватает уровня.

Рассмотрим код

class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

Он идентичен этому коду

class Foo(object):
    def bar(self):
        print "in bar!"
    bar = MyDecorator("foo baby!")(bar)

Итак, MyDecorator.__init__ вызывается с помощью "foo baby!", а затем объект MyDecorator вызывается с помощью функции bar.

Возможно, вы хотите реализовать что-то более похожее на

import functools

def MyDecorator(argument):
    class _MyDecorator(object):
        def __init__(self, fn):
            self.fn = fn

        def __get__(self, obj, type=None):
            return functools.partial(self, obj)

        def __call__(self, *args, **kwargs):
            print "In my decorator before call, with arg %s" % argument
            self.fn(*args, **kwargs)
            print "In my decorator after call, with arg %s" % argument

    return _MyDecorator

Ответ 3

В вашем списке типов декораторов вы пропустили декораторов, которые могут или не могут принимать аргументы. Я думаю, что этот пример охватывает все ваши типы, кроме "декораторов стиля функции (обертывание функции)"

class MyDecorator(object):

    def __init__(self, argument):
        if hasattr('argument', '__call__'):
            self.fn = argument
            self.argument = 'default foo baby'
        else:
            self.argument = argument

    def __get__(self, obj, type=None):
        return functools.partial(self, obj)

    def __call__(self, *args, **kwargs):
        if not hasattr(self, 'fn'):
            self.fn = args[0]
            return self
        print "In my decorator before call, with arg %s" % self.argument
        self.fn(*args, **kwargs)
        print "In my decorator after call, with arg %s" % self.argument


class Foo(object):
    @MyDecorator("foo baby!")
    def bar(self):
        print "in bar!"

class Bar(object):
    @MyDecorator
    def bar(self):
        print "in bar!"

@MyDecorator
def add(a, b):
    print a + b