99

Lets say my class has many methods, and I want to apply my decorator on each one of them, later when I add new methods, I want the same decorator to be applied, but I don't want to write @mydecorator above the method declaration all the time.

If I look into __call__ is that the right way to go?


I'd like to show this way, which is a similar solution to my problem for anybody finding this question later, using a mixin as mentioned in the comments.

class WrapinMixin(object):
    def __call__(self, hey, you, *args):
        print 'entering', hey, you, repr(args)
        try:
            ret = getattr(self, hey)(you, *args)
            return ret
        except:
            ret = str(e)
            raise
        finally:
            print 'leaving', hey, repr(ret)
    

Then you can in another

class Wrapmymethodsaround(WrapinMixin): 
    def __call__:
         return super(Wrapmymethodsaround, self).__call__(hey, you, *args)

Editor's note: this example appears to be solving a different problem than what is asked about.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
rapadura
  • 5,242
  • 7
  • 39
  • 57

5 Answers5

95

Decorate the class with a function that walks through the class's attributes and decorates callables. This may be the wrong thing to do if you have class variables that may happen to be callable, and will also decorate nested classes (credits to Sven Marnach for pointing this out) but generally it's a rather clean and simple solution. Example implementation (note that this will not exclude special methods (__init__ etc.), which may or may not be desired):

def for_all_methods(decorator):
    def decorate(cls):
        for attr in cls.__dict__: # there's propably a better way to do this
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

Use like this:

@for_all_methods(mydecorator)
class C(object):
    def m1(self): pass
    def m2(self, x): pass
    ...
wim
  • 338,267
  • 99
  • 616
  • 750
  • "class variables that may happen to be callable" how is that not a method? – SingleNegationElimination Jun 10 '11 at 14:41
  • 6
    Note that this will also decorate nested classes. (My implementation had the same problem.) – Sven Marnach Jun 10 '11 at 14:41
  • @TokenMacGuy: I assume Python would treat it as a method, too. But OP may not consider it a method of the class. –  Jun 10 '11 at 14:43
  • @Sven: Good catch, although propably negible (nested classes are rare and rarely useful). Added to the answer. –  Jun 10 '11 at 14:44
  • 12
    why not use `inspect.getmembers(cls, inspect.ismethod)` instead of `__dict__` and `callable()` ? of course static method will be out of the question in this case. – mouad Jun 10 '11 at 15:51
  • @mouad: This is precisely the "better way to do this" I hoped for but couldn't think of. –  Jun 10 '11 at 16:28
  • what do you think of the mixin approach? the methods Im interested in wraping/decorating with my own code before executing, still do execute and I dont have to "walk" down a class methods and add decorators, simply using __call__ and inheritance. – rapadura Jun 13 '11 at 20:29
  • @Antonio: I don't see how this mixin would work. Please explain. –  Jun 13 '11 at 20:47
  • 14
    In Python 3 `inspect.getmembers(cls, inspect.ismethod)` won't work because `inspect.ismethod` returns `False` for unbound methods. In Python 2 `inspect.ismethod` returns `True` for unbound methods but `inspect.isfunction` returns `False`. Maybe it's best to write `inspect.getmembers(cls, inspect.isroutine)` instead as that works for both. –  Feb 08 '13 at 12:45
  • With respect to using inspect.getmembers this may be of help: http://stackoverflow.com/questions/1911281/how-do-you-get-list-of-methods-in-a-python-class – Rocketman Mar 09 '13 at 21:16
  • 1
    I'm about to do something similar. Is this still a good method in 2014? Could you update your answer to use inspect rather than the `__dict__` stuff? – wim Jan 02 '14 at 17:33
  • 2
    Using `cls.__dict__` would not decorate inherited methods, but the `inspect` approach would. – Sergey Orshanskiy Jun 10 '15 at 18:38
  • I personally wouldn't WANT to decorate inherited methods, if you wanted that wouldn't you decorate the superclass where they were defined? – Kevin Glasson Jun 11 '20 at 04:12
39

While I'm not fond of using magical approaches when an explicit approach would do, you can probably use a metaclass for this.

def myDecorator(fn):
    fn.foo = 'bar'
    return fn

class myMetaClass(type):
    def __new__(cls, name, bases, local):
        for attr in local:
            value = local[attr]
            if callable(value):
                local[attr] = myDecorator(value)
        return type.__new__(cls, name, bases, local)

class myClass(object):
    __metaclass__ = myMetaClass
    def baz(self):
        print self.baz.foo

and it works as though each callable in myClass had been decorated with myDecorator

>>> quux = myClass()
>>> quux.baz()
bar
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • Can you comment on the mixin approach? – rapadura Jun 13 '11 at 20:30
  • Just a caveat: `callable()` is `True`. This behavior may or may not be desired, depending upon your use case. – Kyle Pittman May 24 '16 at 18:44
  • 1
    I am also looking into this approach and it's great. Here is a good reference discussing metaclasses in general: https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/ One good point mentioned within this reference is that this approach also takes care of subclasses where as, I believe that class decorators do not. This, to me is an important point! – Gregory Kuhn Jul 22 '16 at 12:10
  • 1
    I don't think this is magical at all, rather that metaclasses have a bad PR department. They should be used whenever they are suitable, as is the case here. Good solution! – jhrr Feb 12 '18 at 16:06
  • Notice that for python 3 you should use: `class myClass(metaclass=myMetaClass):` – NoamG Jun 16 '22 at 14:54
17

Not to revive things from the dead, but I really liked delnan's answer, but found it sllliigghhtttlllyy lacking.

def for_all_methods(exclude, decorator):
    def decorate(cls):
        for attr in cls.__dict__:
            if callable(getattr(cls, attr)) and attr not in exclude:
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

EDIT: fix indenting

So you can specify methods//attributes//stuff you don't want decorated

nickneedsaname
  • 731
  • 1
  • 5
  • 22
11

None of the above answers worked for me, since I wanted to also decorate the inherited methods, which was not accomplished by using __dict__, and I did not want to overcomplicate things with metaclasses. Lastly, I am fine with having a solution for Python 2, since I just have an immediate need to add some profiling code for measuring time used by all functions of a class.

import inspect
def for_all_methods(decorator):
    def decorate(cls):
        for name, fn in inspect.getmembers(cls, inspect.ismethod):
            setattr(cls, name, decorator(fn))
        return cls
    return decorate

Source (slightly different solution): https://stackoverflow.com/a/3467879/1243926 There you can also see how to change it for Python 3.

As comments to other answers suggest, consider using inspect.getmembers(cls, inspect.isroutine) instead. If you have found a proper solution that works for both Python 2 and Python 3 and decorates inherited methods, and can still be done in 7 lines, please, edit.

Community
  • 1
  • 1
Sergey Orshanskiy
  • 6,794
  • 1
  • 46
  • 50
5

You could generate a metaclass. This will not decorate inherited methods.

def decorating_meta(decorator):
    class DecoratingMetaclass(type):
        def __new__(self, class_name, bases, namespace):
            for key, value in list(namespace.items()):
                if callable(value):
                    namespace[key] = decorator(value)

            return type.__new__(self, class_name, bases, namespace)

    return DecoratingMetaclass

This will generate a metaclass decorating all methods with the specified function. You can use it in Python 2 or 3 by doing something like this

def doubling_decorator(f):
    def decorated(*a, **kw):
        return f(*a, **kw) * 2
    return decorated

class Foo(dict):
    __metaclass__ = decorating_meta(doubling_decorator)

    def lookup(self, key):
        return self[key]

d = Foo()
d["bar"] = 5
print(d.lookup("bar")) # prints 10
Jeremy
  • 1
  • 85
  • 340
  • 366