Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorating class methods - how to pass the instance to the decorator?

This is Python 2.5, and it's GAE too, not that it matters.

I have the following code. I'm decorating the foo() method in bar, using the dec_check class as a decorator.

class dec_check(object):

  def __init__(self, f):
    self.func = f

  def __call__(self):
    print 'In dec_check.__init__()'
    self.func()

class bar(object):

  @dec_check
  def foo(self):
    print 'In bar.foo()'

b = bar()
b.foo()

When executing this I was hoping to see:

In dec_check.__init__()
In bar.foo()

But I'm getting TypeError: foo() takes exactly 1 argument (0 given) as .foo(), being an object method, takes self as an argument. I'm guessing problem is that the instance of bar doesn't actually exist when I'm executing the decorator code.

So how do I pass an instance of bar to the decorator class?

like image 498
Phil Avatar asked Mar 02 '10 18:03

Phil


People also ask

How do you pass yourself to a decorator?

If you want to access self (instanse where decorator is called from) you can use args[0] inside the decorator.

How do you apply a class method to a decorator in Python?

To decorate a method in a class, first use the '@' symbol followed by the name of the decorator function. A decorator is simply a function that takes a function as an argument and returns yet another function. Here, when we decorate, multiply_together with integer_check, the integer function gets called.

Can a class method be a decorator?

In Python, the @classmethod decorator is used to declare a method in the class as a class method that can be called using ClassName. MethodName() . The class method can also be called using an object of the class. The @classmethod is an alternative of the classmethod() function.

How do you apply a decorator to a classroom?

To decorate a function with a class, we must use the @syntax followed by our class name above the function definition. Following convention, we will use camel-case for our class name. In the class definition, we define two methods: the init constructor and the magic (or dunder) call method.


3 Answers

You need to make the decorator into a descriptor -- either by ensuring its (meta)class has a __get__ method, or, way simpler, by using a decorator function instead of a decorator class (since functions are already descriptors). E.g.:

def dec_check(f):
  def deco(self):
    print 'In deco'
    f(self)
  return deco

class bar(object):
  @dec_check
  def foo(self):
    print 'in bar.foo'

b = bar()
b.foo()

this prints

In deco
in bar.foo

as desired.

like image 144
Alex Martelli Avatar answered Oct 05 '22 07:10

Alex Martelli


Alex's answer suffices when a function is sufficient. However, when you need a class you can make it work by adding the following method to the decorator class.

def __get__(self, obj, objtype):
    """Support instance methods."""
    import functools
    return functools.partial(self.__call__, obj)

To understand this you need to understand the descriptor protocol. The descriptor protocol is the mechanism for binding a thing to an instance. It consists of __get__, __set__ and __delete__, which are called when the thing is got, set or deleted from the instances dictionary.

In this case when the thing is got from the instance we are binding the first argument of its __call__ method to the instance, using partial. This is done automatically for member functions when the class is constructed, but for a synthetic member function like this we need to do it explicitly.

like image 37
Gordon Wrigley Avatar answered Oct 05 '22 09:10

Gordon Wrigley


If you want to write the decorator as a class you can do:

from functools import update_wrapper, partial

class MyDecorator(object):
    def __init__(self, func):
        update_wrapper(self, func)
        self.func = func

    def __get__(self, obj, objtype):
        """Support instance methods."""
        return partial(self.__call__, obj)

    def __call__(self, obj, *args, **kwargs):
        print('Logic here')
        return self.func(obj, *args, **kwargs)

my_decorator = MyDecorator

class MyClass(object):
     @my_decorator
     def my_method(self):
         pass
like image 30
Thomas Turner Avatar answered Oct 05 '22 09:10

Thomas Turner