Dekorator oparty na klasach Pythona z parametrami, które mogą ozdobić metodę lub funkcję

Widziałem wiele przykładów dekoratorów Pythona, które są:

  • dekoratory stylu funkcji (zawijanie funkcji)
  • dekoratorzy stylu klasy (realizujący __init__, __get__, i __call__)
  • dekoratorzy, którzy nie przyjmują argumentów
  • dekoratorzy, którzy biorą argumenty
  • dekoratory, które są "przyjazne metodom" (tj. mogą ozdobić metodę w klasie)
  • dekoratory, które są "przyjazne dla funkcji "( mogą ozdobić prostą funkcję
  • dekoratorzy, którzy mogą udekoruj zarówno metody, jak i funkcje

Ale nigdy nie widziałem żadnego przykładu, który mógłby zrobić wszystkie powyższe, i mam problem z syntetyzowaniem różnych odpowiedzi na konkretne pytania (takie jak ten, ten , lub ten (który ma jedną z najlepszych odpowiedzi, jakie kiedykolwiek widziałem na SO) ), Jak połączyć wszystkie powyższe.

What I want is a oparte na klasach dekorator, który może ozdobić albo metodą, albo funkcja, oraz to wymaga co najmniej jednego dodatkowego parametru. Ie tak, aby działały:

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

I spodziewałbym się zobaczyć:

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!

Edit: jeśli to ma znaczenie, używam Pythona 2.7.

Author: Community, 2012-02-23

3 answers

Nie musisz zadzierać z deskryptorami. Wystarczy utworzyć funkcję wrappera wewnątrz metody __call__() i zwrócić ją. Standardowe funkcje Pythona mogą zawsze działać jako metoda lub funkcja, w zależności od kontekstu:

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

Trochę wyjaśnienia o tym, co się dzieje, gdy ten dekorator jest używany w ten sposób:

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

Pierwsza linia tworzy instancję MyDecorator i przekazuje "some other func!" jako argument do __init__(). Nazwijmy tę instancję my_decorator. Następny, niedekorowany obiekt function -- nazwijmy go bare_func -- jest tworzony i przekazywany do instancji dekoratora, więc my_decorator(bare_func) jest wykonywany. Spowoduje to wywołanie MyDecorator.__call__(), które utworzy i zwróci funkcję wrappera. Na koniec funkcja ta jest przypisana do nazwy some_other_function.

 32
Author: Sven Marnach,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-23 16:47:07

Brakuje Ci poziomu.

Rozważmy kod

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

Jest identyczny z tym kodem

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

Więc MyDecorator.__init__ jest wywoływany przez "foo baby!", a następnie MyDecorator obiekt jest wywoływany przez funkcję bar.

Być może chcesz zaimplementować coś bardziej jak

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
 10
Author: Mike Graham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-10 05:00:01

Na liście typów dekoratorów przegapiłeś dekoratorów, którzy mogą lub nie mogą przyjmować argumentów. Myślę, że ten przykład obejmuje wszystkie typy z wyjątkiem " dekoratorów stylu funkcji (zawijanie funkcji)"

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
 4
Author: Vinayak Kaniyarakkal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-22 09:53:29