Python Decorate all methods of subclass, and provide means to override
Asked Answered
A

1

7

I am working on finding a way to reduce boilerplate decorators. We have a lot of classes that use a @decorate. For example:

class MyClass(Base):
     @decorate
     def fun1(self):
         pass
     @decorate
     def fun2(self):
         pass
     def fun3(self):
         pass

I want to make it so by default the decorator is there, unless someone specifies otherwise.


I use this code to do the autowrap

from functools import wraps

def myDecorator(func):
    @wraps(func)
    def decorator(self, *args, **kwargs):
        try:
            print 'enter'
            ret = func(self, *args, **kwargs)
            print 'leave'
        except:
            print 'exception'
            ret = None

        return ret

    return decorator

class TestDecorateAllMeta(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 TestClass(object):
    __metaclass__ = TestDecorateAllMeta

    def test_print2(self, val):
        print val

    def test_print(self, val):
        print val

c = TestClass()
c.test_print1("print 1")
c.test_print2("print 2")

My question are:

  1. Is there a better way to accompish auto-decorating?
  2. How can I go about overriding?

Ideally my end solution would be something like:

class TestClass(object):
    __metaclass__ = TestDecorateAllMeta

    def autowrap(self):
        print("Auto wrap")

    @dont_decorate
    def test_dont_decorate(self, val):
        print val

Edit

To speak to one of the comments below, since classess are callable instead of doing

if callable(value):

It should read:

if isinstance(value,types.FunctionType) 
Augustina answered 23/11, 2011 at 15:51 Comment(1)
Explicit is better than implicit. Unless there's a lot of boilerplace (a dozen nontrivial lines per method), I'd shy away from metaclass magic. Heck, even then I'd prefer a simpler solution whenever it's possible.Roadability
U
4

Rather than making the user of my class specify a __metaclass__ attribute I would just have them derive from my base class that defines it. No need to expose the plumbing unnecessarily.

Other than that, looks good, though. Your @dont_decorate function decorator can be implemented by setting an attribute on the original function, which your class decorator then detects and skips the decoration if it is present.

def dont_decorate(func):
    func._dont_decorate = True
    return func

Then in your metaclass, where you now have the line if callable(value): just put:

if callable(value) and not hasttr(value, "_dont_decorate"):

As an aside, classes are callable so if you don't want inner classes decorated, you should probably check for functions using isinstance() rather than callable().

If you are interested in more explicit alternatives, you might take a look at this recent question where someone wanted to do essentially the same thing using a class decorator. Unfortunately, this is a good bit more complicated because the methods are already wrapped by the time the decorator sees them.

Ustkamenogorsk answered 23/11, 2011 at 16:21 Comment(9)
So the reason I put meta class in the child class was so it only decorates the members of the child class. If i put meta in the parent it will decorate all function types... do you have an easy way to detect if the Function being called belongs to the subclass?Augustina
You could put @dont_decorate on all the functions in the parent class. :-)Ustkamenogorsk
That gets annoying :) and I believe thats how I got to this point... I could create an intermediate class...Augustina
Or a cleaner way: declare one class with the methods, then a second class that inherits from that and defines __metaclass__. Then have users inherit from the second class.Ustkamenogorsk
Yes, you wrote your "intermediate class" comment while I was writing my second one! :-)Ustkamenogorsk
Thanks for the help and advice. I updated above to show the isinstance vs callable. Just so people can see what you were talking about.Augustina
Thought of a way to eliminate that intermediate class: have your metaclass check to see if your base class's name is in globals() before applying the decorator. If it's not, then it's being used on the base class and you shouldn't decorate.Ustkamenogorsk
Not sure if i follow? You talking about a if not globals().has_key("<base_class_name>") ?Augustina
Yeah... if "base_class_name" in globals() then apply the decorator ... if the base class name is not in globals() it means we're defining the base class right now, and so should not apply the decorator.Ustkamenogorsk

© 2022 - 2024 — McMap. All rights reserved.