Dekoratory Pythona w klasach


140

Czy można napisać coś takiego:

class Test(object):
    def _decorator(self, foo):
        foo()

    @self._decorator
    def bar(self):
        pass

To się nie udaje: jaźń w sobie jest nieznana

Próbowałem też:

@Test._decorator(self)

co również kończy się niepowodzeniem: Test nieznany

Chciałbym tymczasowo zmienić niektóre zmienne instancji w dekoratorze, a następnie uruchomić metodę dekorowania, zanim zmienię je z powrotem.

Odpowiedzi:


268

Czy coś takiego zrobi to, czego potrzebujesz?

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

test = Test()

test.bar()

Pozwala to uniknąć wywołania self w celu uzyskania dostępu do dekoratora i pozostawia go ukrytym w przestrzeni nazw klas jako zwykłą metodę.

>>> import stackoverflow
>>> test = stackoverflow.Test()
>>> test.bar()
start magic
normal call
end magic
>>> 

zredagowano, by odpowiedzieć na pytanie w komentarzach

Jak korzystać z ukrytego dekoratora w innej klasie

class Test(object):
    def _decorator(foo):
        def magic( self ) :
            print "start magic"
            foo( self )
            print "end magic"
        return magic

    @_decorator
    def bar( self ) :
        print "normal call"

    _decorator = staticmethod( _decorator )

class TestB( Test ):
    @Test._decorator
    def bar( self ):
        print "override bar in"
        super( TestB, self ).bar()
        print "override bar out"

print "Normal:"
test = Test()
test.bar()
print

print "Inherited:"
b = TestB()
b.bar()
print

Wynik:

Normal:
start magic
normal call
end magic

Inherited:
start magic
override bar in
start magic
normal call
end magic
override bar out
end magic

7
Dekorator czy funkcja dekoracyjna? Zwróć uwagę, że zwrócona funkcja „magiczna”, która zawija pasek, otrzymuje zmienną self powyżej, gdy „bar” jest wywoływana na instancji i może zrobić wszystko ze zmiennymi instancji przed i po (lub nawet niezależnie od tego, czy nazywa się „bar”) . Podczas deklarowania klasy nie ma czegoś takiego jak zmienne instancji. Czy chciałeś coś zrobić klasie z poziomu dekoratora? Nie sądzę, żeby to było idiomatyczne użycie.
Michael Speer

1
Dzięki Michael, dopiero teraz zobaczyłem, że tego właśnie chciałem.
hcvst

2
Uważam, że to rozwiązanie jest o wiele przyjemniejsze niż przyjęta odpowiedź, ponieważ pozwala na użycie składni @ decorator w punkcie definicji. Jeśli muszę umieścić wywołania dekoratora na końcu zajęć, nie jest jasne, czy funkcje są dekorowane. To trochę dziwne, że nie możesz użyć @staticmethod na samym dekoratorze, ale przynajmniej działa.
mgiuca

1
Nie sądzę, żeby to działało, jeśli utworzę dziedziczoną klasę Test.Na przykład: class TestB (Test): @_decorator def foobar (self): print "adsf" Czy istnieje obejście?
extraeee

1
@extraeee: sprawdź wprowadzone przeze mnie zmiany. musisz zakwalifikować dany dekorator jako metodę statyczną, ale dopiero gdy skończysz go używać (lub przypiszesz wersję metody statycznej do innej nazwy)
Michael Speer

58

To, czego chcesz, nie jest możliwe. Weźmy na przykład, czy poniższy kod wygląda na prawidłowy:

class Test(object):

    def _decorator(self, foo):
        foo()

    def bar(self):
        pass
    bar = self._decorator(bar)

To oczywiście nie jest poprawne, ponieważ selfnie jest zdefiniowane w tym momencie. To samo dotyczy Testtego, że nie zostanie zdefiniowana, dopóki nie zostanie zdefiniowana sama klasa (która jest w trakcie). Pokazuję ci ten fragment kodu, ponieważ w to właśnie przekształca się Twój fragment dekoratora.

Tak więc, jak widać, dostęp do instancji w takim dekoratorze nie jest tak naprawdę możliwy, ponieważ dekoratory są stosowane podczas definiowania dowolnej funkcji / metody, do której są dołączone, a nie podczas tworzenia instancji.

Jeśli potrzebujesz dostępu na poziomie klasy , spróbuj tego:

class Test(object):

    @classmethod
    def _decorator(cls, foo):
        foo()

    def bar(self):
        pass
Test.bar = Test._decorator(Test.bar)

5
prawdopodobnie powinien zostać zaktualizowany, aby odwołać się do dokładniejszej odpowiedzi poniżej
Nathan Buesgens

1
Miły. Twoja proza ​​mówi, że niemożliwe, ale Twój kod prawie pokazuje, jak to zrobić.
Mad Physicist

22
import functools


class Example:

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, *args, **kwargs)
        return wrap

    @wrapper
    def method(self):
        print("METHOD")

    wrapper = staticmethod(wrapper)


e = Example()
e.method()

1
TypeError: Obiekt „staticmethod” nie jest wywoływany
wyx

@wyx nie dzwoń do dekoratora. Na przykład, powinno być @foo, a nie@foo()
docyoda

Nie powinno pierwszy argument wrapperbyć self?
CpILL

7

Używam tego typu dekoratora w niektórych sytuacjach debugowania, umożliwia on nadpisywanie właściwości klas poprzez dekorowanie, bez konieczności znajdowania funkcji wywołującej.

class myclass(object):
    def __init__(self):
        self.property = "HELLO"

    @adecorator(property="GOODBYE")
    def method(self):
        print self.property

Oto kod dekoratora

class adecorator (object):
    def __init__ (self, *args, **kwargs):
        # store arguments passed to the decorator
        self.args = args
        self.kwargs = kwargs

    def __call__(self, func):
        def newf(*args, **kwargs):

            #the 'self' for a method function is passed as args[0]
            slf = args[0]

            # replace and store the attributes
            saved = {}
            for k,v in self.kwargs.items():
                if hasattr(slf, k):
                    saved[k] = getattr(slf,k)
                    setattr(slf, k, v)

            # call the method
            ret = func(*args, **kwargs)

            #put things back
            for k,v in saved.items():
                setattr(slf, k, v)

            return ret
        newf.__doc__ = func.__doc__
        return newf 

Uwaga: ponieważ użyłem dekoratora klas, musisz użyć @adecorator () z nawiasami kwadratowymi, aby ozdobić funkcje, nawet jeśli nie przekażesz żadnych argumentów do konstruktora klasy dekoratora.


7

Jest to jeden ze sposobów dostępu (i był używany) selfz wnętrza decoratorzdefiniowanej w tej samej klasie:

class Thing(object):
    def __init__(self, name):
        self.name = name

    def debug_name(function):
        def debug_wrapper(*args):
            self = args[0]
            print 'self.name = ' + self.name
            print 'running function {}()'.format(function.__name__)
            function(*args)
            print 'self.name = ' + self.name
        return debug_wrapper

    @debug_name
    def set_name(self, new_name):
        self.name = new_name

Wyjście (testowane Python 2.7.10):

>>> a = Thing('A')
>>> a.name
'A'
>>> a.set_name('B')
self.name = A
running function set_name()
self.name = B
>>> a.name
'B'

Powyższy przykład jest głupi, ale działa.


4

Znalazłem to pytanie, badając bardzo podobny problem. Moim rozwiązaniem jest podzielenie problemu na dwie części. Najpierw musisz przechwycić dane, które chcesz skojarzyć z metodami klas. W takim przypadku funkcja handler_for skojarzy polecenie systemu Unix z obsługą danych wyjściowych tego polecenia.

class OutputAnalysis(object):
    "analyze the output of diagnostic commands"
    def handler_for(name):
        "decorator to associate a function with a command"
        def wrapper(func):
            func.handler_for = name
            return func
        return wrapper
    # associate mount_p with 'mount_-p.txt'
    @handler_for('mount -p')
    def mount_p(self, slurped):
        pass

Teraz, gdy powiązaliśmy pewne dane z każdą metodą klasową, musimy zebrać te dane i przechowywać je w atrybucie klasy.

OutputAnalysis.cmd_handler = {}
for value in OutputAnalysis.__dict__.itervalues():
    try:
        OutputAnalysis.cmd_handler[value.handler_for] = value
    except AttributeError:
        pass

4

Oto rozszerzenie odpowiedzi Michaela Speera, aby pójść o kilka kroków dalej:

Dekorator metody instancji, który przyjmuje argumenty i działa na funkcji z argumentami i wartością zwracaną.

class Test(object):
    "Prints if x == y. Throws an error otherwise."
    def __init__(self, x):
        self.x = x

    def _outer_decorator(y):
        def _decorator(foo):
            def magic(self, *args, **kwargs) :
                print("start magic")
                if self.x == y:
                    return foo(self, *args, **kwargs)
                else:
                    raise ValueError("x ({}) != y ({})".format(self.x, y))
                print("end magic")
            return magic

        return _decorator

    @_outer_decorator(y=3)
    def bar(self, *args, **kwargs) :
        print("normal call")
        print("args: {}".format(args))
        print("kwargs: {}".format(kwargs))

        return 27

I wtedy

In [2]:

    test = Test(3)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    normal call
    args: (13, 'Test')
    kwargs: {'q': 9, 'lollipop': [1, 2, 3]}
Out[2]:
    27
In [3]:

    test = Test(4)
    test.bar(
        13,
        'Test',
        q=9,
        lollipop=[1,2,3]
    )
    
    start magic
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-3-576146b3d37e> in <module>()
          4     'Test',
          5     q=9,
    ----> 6     lollipop=[1,2,3]
          7 )

    <ipython-input-1-428f22ac6c9b> in magic(self, *args, **kwargs)
         11                     return foo(self, *args, **kwargs)
         12                 else:
    ---> 13                     raise ValueError("x ({}) != y ({})".format(self.x, y))
         14                 print("end magic")
         15             return magic

    ValueError: x (4) != y (3)

3

Dekoratory wydają się lepiej przystosowane do modyfikowania funkcjonalności całego obiektu (łącznie z obiektami funkcyjnymi) w porównaniu z funkcjonalnością metody obiektu, która na ogół będzie zależeć od atrybutów instancji. Na przykład:

def mod_bar(cls):
    # returns modified class

    def decorate(fcn):
        # returns decorated function

        def new_fcn(self):
            print self.start_str
            print fcn(self)
            print self.end_str

        return new_fcn

    cls.bar = decorate(cls.bar)
    return cls

@mod_bar
class Test(object):
    def __init__(self):
        self.start_str = "starting dec"
        self.end_str = "ending dec" 

    def bar(self):
        return "bar"

Wynik to:

>>> import Test
>>> a = Test()
>>> a.bar()
starting dec
bar
ending dec

1

Możesz ozdobić dekoratora:

import decorator

class Test(object):
    @decorator.decorator
    def _decorator(foo, self):
        foo(self)

    @_decorator
    def bar(self):
        pass

1

Mam implementację dekoratorów, które mogą pomóc

    import functools
    import datetime


    class Decorator(object):

        def __init__(self):
            pass


        def execution_time(func):

            @functools.wraps(func)
            def wrap(self, *args, **kwargs):

                """ Wrapper Function """

                start = datetime.datetime.now()
                Tem = func(self, *args, **kwargs)
                end = datetime.datetime.now()
                print("Exection Time:{}".format(end-start))
                return Tem

            return wrap


    class Test(Decorator):

        def __init__(self):
            self._MethodName = Test.funca.__name__

        @Decorator.execution_time
        def funca(self):
            print("Running Function : {}".format(self._MethodName))
            return True


    if __name__ == "__main__":
        obj = Test()
        data = obj.funca()
        print(data)

1

Zadeklaruj w klasie wewnętrznej. To rozwiązanie jest całkiem solidne i polecane.

class Test(object):
    class Decorators(object):
    @staticmethod
    def decorator(foo):
        def magic(self, *args, **kwargs) :
            print("start magic")
            foo(self, *args, **kwargs)
            print("end magic")
        return magic

    @Decorators.decorator
    def bar( self ) :
        print("normal call")

test = Test()

test.bar()

Wynik:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.