Mark Grey
Mark Grey

Reputation: 10257

Python decorate method with callable outside parent class

Let's assume I have a class to be used as a decorator like such:

class specialmethod:
  def __init__(self,func):
     self.func = func

  def __call__(self,arg1,arg2):
     print arg1,arg2
     self.func(arg1,arg2)

But the method being decorated is within another class, that also must utilize self (the values within self are relevant to the function body post-decoration)

class A:
  @specialmethod
  def dosomething(self,data,otherdata):
    #this guy down here isn't getting the right value for self, an instance of A
    print type(self)

Traceback (most recent call last):
  File "deco.py", line 18, in <module>
    a.dosomething('foo','bar')
  File "deco.py", line 7, in __call__
    self.func(arg1,arg2)
TypeError: dosomething() takes exactly 3 arguments (2 given)

Is there anyway to preserve the way self would work if it were not decorated? I've tried passing it explicitly but it still expects the wrong number of parameters.

EDIT

I should clarify that the intended value for self in the final function body of dosomething should refer to the instance of A... not looking to get the decorator instance into the decorated function.

Upvotes: 3

Views: 1655

Answers (1)

Thomas Wouters
Thomas Wouters

Reputation: 133415

When you use a class like a decorator, you have to realize that the normal magic that turns functions into methods (the descriptor protocol, specifically the __get__ method) won't work for your class unless you explicitly implement it. You could do this manually, by implementing a __get__ method that returns a new type that remembers the instance it was retrieved from as well as the actual callable to call, but it's a lot of work for very little gain.

A more common approach is to not use an actual class as a decorator, and always have the decorator return an actual Python function. In this particular case, there isn't really a point in keeping the class at all, so you could just do:

def specialmethod(f):
    @functools.wraps(f)
    def wrapper(arg1, arg2):
        print arg1, arg2
        f(arg1, arg2)
    return wrapper

... but if you really want to keep the class, you can do so by doing something like:

class _specialmethod:
  def __init__(self,func):
     self.func = func

  def __call__(self,arg1,arg2):
     print arg1,arg2
     self.func(arg1,arg2)

def specialmethod(f):
    wrapper_class = _specialmethod(f)
    @functools.wraps(f)
    def wrapper(arg1, arg2):
        wrapper_class(arg1, arg2)
    return wrapper

Upvotes: 4

Related Questions