Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decorate methods per instance in Python

Assume I have some simple class

class TestClass:
    def doSomething(self):
        print 'Did something'

I would like to decorate the doSomething method, for example to count the number of calls

class SimpleDecorator(object):
    def __init__(self,func):
        self.func=func
        self.count=0
    def __get__(self,obj,objtype=None):
        return MethodType(self,obj,objtype)
    def __call__(self,*args,**kwargs):
        self.count+=1
        return self.func(*args,**kwargs)

Now this counts the number of calls to the decorated method, however I would like to have per-instance counter, such that after

foo1=TestClass()
foo1.doSomething()
foo2=TestClass()

foo1.doSomething.count is 1 and foo2.doSomething.count is 0. From what I understand, this is not possible using decorators. Is there some way to achieve such behaviour?

like image 548
Christoph Avatar asked Jun 15 '11 11:06

Christoph


People also ask

How do you decorate a method 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.

Can we use 2 decorators in Python?

Python allows us to implement more than one decorator to a function. It makes decorators useful for reusable building blocks as it accumulates several effects together. It is also known as nested decorators in Python.

What is method decorator in Python?

A decorator is a design pattern in Python that allows a user to add new functionality to an existing object without modifying its structure. Decorators are usually called before the definition of a function you want to decorate.

How do you decorate a class in Python?

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.


2 Answers

Utilize the fact that self (i.e. the object which the method is invoked on) is passed as a parameter to the method:

import functools

def counted(method):
    @functools.wraps(method)
    def wrapped(obj, *args, **kwargs):
        if hasattr(obj, 'count'): 
            obj.count += 1
        else:
            obj.count = 1
        return method(obj, *args, **kwargs)
    return wrapped

In above code, we intercept the object as obj parameter of the decorated version of method. Usage of the decorator is pretty straightforward:

class Foo(object):
    @counted
    def do_something(self): pass
like image 108
Xion Avatar answered Sep 20 '22 19:09

Xion


Wouldn't the first element of *args be the object the method is being invoked on? Can't you just store the count there?

like image 22
Daren Thomas Avatar answered Sep 18 '22 19:09

Daren Thomas