Декораторы Python в классах


148

Можно ли написать что-нибудь вроде:

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

    @self._decorator
    def bar(self):
        pass

Это не удается: я в @self неизвестен

Я также пробовал:

@Test._decorator(self)

что также не удается: Тест неизвестен

Я хотел бы временно изменить некоторые переменные экземпляра в декораторе, а затем запустить декорированный метод, прежде чем изменять их обратно.

Ответы:


278

Что-то вроде этого сделало бы то, что вам нужно?

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

Это позволяет избежать вызова self для доступа к декоратору и оставляет его скрытым в пространстве имен класса как обычный метод.

>>> 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
Декоратор или декорированная функция? Обратите внимание, что возвращенная «волшебная» функция, которая обертывает bar, получает собственную переменную выше, когда «bar» вызывается в экземпляре, и может делать что угодно с переменными экземпляра, которые она хотела, до и после (или даже независимо от того, вызывала ли она «bar») . При объявлении класса не существует такой вещи, как переменные экземпляра. Вы хотели что-то сделать с классом из декоратора? Я не думаю, что это идиоматическое употребление.
Майкл Спир,

1
Спасибо Михаилу, только сейчас увидел, что это то, что я хотел.
hcvst

2
Я считаю, что это решение намного лучше, чем принятый ответ, потому что оно позволяет использовать синтаксис @ decorator в точке определения. Если мне нужно поместить вызовы декораторов в конец класса, тогда неясно, украшаются ли функции. Немного странно, что вы не можете использовать @staticmethod в самом декораторе, но, по крайней мере, он работает.
mgiuca

1
Я не думаю, что это сработает, если я создам унаследованный класс Test. Например: class TestB (Test): @_decorator def foobar (self): print "adsf" Есть ли обходной путь?
extraeee

1
@extraeee: проверьте мои изменения. вам необходимо квалифицировать данный декоратор как статический метод, но только после того, как вы закончите его использовать (или присвоите версии статического метода другое имя)
Майкл Спир

59

То, что вы хотите сделать, невозможно. Возьмем, к примеру, выглядит ли приведенный ниже код действительным:

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
Ницца. Ваша проза говорит о невозможности, но ваш код в значительной степени показывает, как это сделать.
Безумный физик

23
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'

Приведенный выше пример глуп, но он работает.


5

Я нашел этот вопрос, исследуя очень похожую проблему. Мое решение - разбить проблему на две части. Во-первых, вам нужно захватить данные, которые вы хотите связать с методами класса. В этом случае 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.