Декоратори пітона на заняттях


140

Чи можна написати щось на кшталт:

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

    @self._decorator
    def bar(self):
        pass

Це не вдається: «Я в собі» невідомий

Я також спробував:

@Test._decorator(self)

який також не вдається: Тест невідомий

Я хотів би тимчасово змінити деякі змінні екземпляра в декораторі, а потім запустити оформлений метод, перш ніж змінити їх назад.

Відповіді:


268

Чи щось подібне робитиме те, що вам потрібно?

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

Це дозволяє уникнути заклику самостійно отримати доступ до декоратора і залишає його прихованим у просторі імен класу як звичайний метод.

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

редагували, щоб відповісти на запитання в коментарях:

Як використовувати прихований декоратор в іншому класі

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

Вихід:

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
Декоратор чи прикрашена функція? Зауважте, що повернута "магічна" функція, яка обгортає панель, отримує змінну self вище, коли "bar" викликається в екземплярі, і може зробити все, що потрібно для змінних екземплярів, які вона хотіла до і після (або навіть, чи ні) називати "bar" . Немає такого поняття, як змінні екземпляра при оголошенні класу. Ви хотіли щось зробити для класу з-під декоратора? Я не думаю, що це ідіоматичне вживання.
Майкл Шпіер

1
Дякую Майклу, тільки зараз побачив, що це те, чого я хотів.
hcvst

2
Я вважаю це рішення набагато приємнішим за прийняту відповідь, оскільки воно дозволяє використовувати синтаксис @ decorator у точці визначення. Якщо мені доведеться ставити виклики декораторів в кінці заняття, то не зрозуміло, що функції оформляються. Це трохи дивно, що ви не можете використовувати @staticmethod для самого декоратора, але принаймні це працює.
mgiuca

1
Я не думаю, що це спрацьовує, якщо я створюю спадковий клас Test.Наприклад: клас TestB (Тест): @_decorator def foobar (self): print "adsf" Чи є спосіб вирішення?
extraeee

1
@extraeee: перевірте внесені вами зміни. вам потрібно кваліфікувати даний декоратор як статичний метод, але лише після того, як ви закінчите його використовувати (або призначити версію staticmethod іншому імені)
Michael Speer

58

Те, що ти хочеш зробити, неможливо. Візьмемо, наприклад, чи дійсний код нижче:

class Test(object):

    def _decorator(self, foo):
        foo()

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

Це, звичайно, не вірно, оскільки selfв цьому моменті не визначено. Те саме стосується того Test, що він не буде визначений, поки не буде визначений сам клас (який його у процесі). Я показую вам цей фрагмент коду, тому що саме в цей фрагмент перетворюється ваш декоратор.

Отже, як ви бачите, доступ до екземпляра в декоративному інструменті подібний дійсно неможливий, оскільки декоратори застосовуються під час визначення будь-якої функції / методу, до якого вони додані, а не під час створення інстанцій.

Якщо вам потрібен доступ на рівні класу , спробуйте це:

class Test(object):

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

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

5
можливо, слід оновити, щоб посилатися на більш точну відповідь нижче
Натан Буесген

1
Приємно. Ваша проза говорить неможливо, але ваш код майже показує, як це зробити.
Божевільний фізик

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: об’єкт 'staticmethod' не можна
викликати

@wyx не дзвонить декоратору. Наприклад, так і має бути @foo, ні@foo()
docyoda

Чи не слід перший аргумент wrapperбути self?
CpILL

7

Я використовую цей тип декоратора в деяких ситуаціях налагодження, він дозволяє переосмислювати властивості класу шляхом прикраси, не знаходячи функції виклику.

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

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

Ось код декоратора

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 

Примітка: оскільки я використовував декоратор класу, вам потрібно буде використовувати @adecorator () з дужками для прикраси функцій, навіть якщо ви не передаєте жодних аргументів конструктору класу декораторів.


7

Це один із способів доступу (і використовувався) selfвсередині decoratorвизначеного всередині одного класу:

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

Вихід (тестований на 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'

Наведений вище приклад - нерозумний, але він працює.


4

Я знайшов це питання під час дослідження дуже подібної проблеми. Моє рішення - розділити проблему на дві частини. По-перше, вам потрібно зафіксувати дані, які потрібно пов’язати з методами класу. У цьому випадку handler_for зв'яже команду Unix з обробником для виведення цієї команди.

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

Тепер, коли ми пов’язали деякі дані з кожним методом класу, нам потрібно зібрати ці дані та зберегти їх у атрибуті класу.

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

4

Ось розширення відповіді Майкла Шпіра, щоб зробити його на кілька кроків далі:

Декоратор методу екземпляра, який бере аргументи та діє на функцію з аргументами та зворотним значенням.

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

І потім

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

Декоратори здаються більш підходящими для зміни функціональності цілого об'єкта (включаючи об’єкти функцій) порівняно з функціональністю об'єктного методу, який взагалі залежатиме від атрибутів екземпляра. Наприклад:

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"

Вихід:

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

1

Можна прикрасити декоратора:

import decorator

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

    @_decorator
    def bar(self):
        pass

1

У мене є реалізація декораторів, які можуть допомогти

    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

Декларуйте у внутрішньому класі. Це рішення досить міцне і рекомендується.

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

Результат:

>>> test = Test()
>>> test.bar()
start magic
normal call
end magic
>>> 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.