12

I have a class defined as follows:

class SomeViewController(BaseViewController):
    @requires('id', 'param1', 'param2')
    @ajaxGet
    def create(self):
        #do something here

Is it possible to write a decorator function that:

  1. Takes a list of args, and possibly kwargs, and
  2. Accesses the instance of the class the method its decorating is defined in?

So for the @ajaxGet decorator, there is a attribute in self called type which contains the value I need to check.

Thanks

Michael Bates
  • 1,884
  • 2
  • 29
  • 40
  • 1
    Depends what you mean. The decorator itself can't have access to `self`, since the decoration happens during the definition of the class, before any instances exist. But the wrapped function that is the result of the decorator will know the instance when it is called. Can you give an example of what you want to achieve? – BrenBarn Mar 04 '13 at 06:35
  • So when I call `create` on `SomeViewController` the decorated method(s) has access to `self` (which is passed into `create`, so using that object is a possibility), and can also take arguments to perform checks against other attributes on `self` – Michael Bates Mar 04 '13 at 06:45

1 Answers1

18

Yes. In fact, in the sense you seem to mean, there isn't really a way to write a decorator that doesn't have access to self. The decorated function wraps the original function, so it has to accept at least the arguments that that function accepts (or some arguments from which those can be derived), otherwise it couldn't pass the right arguments to the underlying function.

There is nothing special you need to do to do this, just write an ordinary decorator:

def deco(func):
    def wrapper(self, *args, **kwargs):
        print "I am the decorator, I know that self is", self, "and I can do whatever I want with it!"
        print "I also got other args:", args, kwargs
        func(self)
    return wrapper

class Foo(object):
    @deco
    def meth(self):
        print "I am the method, my self is", self

Then you can just use it:

>>> f = Foo()
>>> f.meth()
I am the decorator, I know that self is <__main__.Foo object at 0x0000000002BCBE80> and I can do whatever I want with it!
I also got other args: () {}
I am the method, my self is <__main__.Foo object at 0x0000000002BCBE80>
>>> f.meth('blah', stuff='crud')
I am the decorator, I know that self is <__main__.Foo object at 0x0000000002BCBE80> and I can do whatever I want with it!
I also got other args: (u'blah',) {'stuff': u'crud'}
I am the method, my self is <__main__.Foo object at 0x0000000002BCBE80>
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
  • Thanks for that. Is there a way to make the decorator take arguments for checking? If you can answer that I'll make your answer the accepted answer. – Michael Bates Mar 04 '13 at 07:32
  • What do you mean "take arguments for checking"? – BrenBarn Mar 04 '13 at 07:56
  • The decorator has to take some arguments, to be then checked in the decorator function against `self`. I worked it out though, the decorator function has to be wrapped in another function. – Michael Bates Mar 04 '13 at 08:00
  • 1
    I think he ment something like this "@requires('id', 'param1', 'param2')". If so, then question isn't duplicated and you just need to wrap decorator from this post with additional function that takes parameters which can be used inside any function within it. Eg. def decorator(foo): def meth_wrap(meth): def params_wrap(self, *args, **kwargs): self.foo = foo return meth(self, *args, **kwargs) return params_wrap return meth_wrap – Karolius Dec 27 '19 at 12:58