How to decorate all functions of a class without typing it over and over for each method?

42,721

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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
Share:
42,721
rapadura
Author by

rapadura

love gnu/linux and free-software

Updated on January 09, 2020

Comments

  • rapadura
    rapadura over 4 years

    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 dont want to write @mydecorator above the method declaration all the time?

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

    IMPORTANT: the example below appears to be solving a different problem than the original question asked about.

    EDIT: Id like to show this way, which is a similar solution to my problem for anyobody 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)
    
  • rapadura
    rapadura about 13 years
    Thats also interesting, thanks!
  • rapadura
    rapadura about 13 years
    Can you comment on the mixin approach?
  • nickneedsaname
    nickneedsaname over 12 years
    actually you can really go nuts. You can have an include instead of an exclude (but not both because that doesn't make sense...) and stuff. But this is pretty powerful magic here.
  • rapadura
    rapadura over 12 years
    Improvements are always welcome, someone may find it useful with time
  • nickneedsaname
    nickneedsaname over 12 years
    That's what I was hoping for! Thanks for the vote of confidence :P
  • Kyle Pittman
    Kyle Pittman about 8 years
    Just a caveat: callable(<class>) is True. This behavior may or may not be desired, depending upon your use case.
  • Gregory Kuhn
    Gregory Kuhn almost 8 years
    I am also looking into this approach and it's great. Here is a good reference discussing metaclasses in general: 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!
  • jhrr
    jhrr over 6 years
    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!
  • davegallant
    davegallant about 6 years
    I like this. maybe exclude should default to None though. :D
  • TheEagle
    TheEagle almost 3 years
    @davegallant "maybe exclude should default to None though" - that would not be a good idea - TypeError: NoneType object is not iterable
  • davegallant
    davegallant almost 3 years
    I'm trying to remember why I wrote this. I think I probably meant an empty list [].
  • NoamG
    NoamG about 2 years
    Notice that for python 3 you should use: class myClass(metaclass=myMetaClass):