Python Class Based Decorator с параметрами, которые могут украшать метод или функцию - PullRequest
37 голосов
/ 23 февраля 2012

Я видел много примеров 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.

Ответы [ 3 ]

34 голосов
/ 23 февраля 2012

Вам не нужно возиться с дескрипторами.Достаточно создать функцию-обертку внутри метода __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.Затем создается недекорированный объект функции - давайте назовем его bare_func - и передаем его экземпляру декоратора, поэтому выполняется my_decorator(bare_func).Это вызовет MyDecorator.__call__(), который создаст и вернет функцию-оболочку.Наконец, этой функции-обертке присваивается имя some_other_function.

11 голосов
/ 23 февраля 2012

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

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

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
4 голосов
/ 22 сентября 2017

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

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
...