Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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) 
like image 702
Nix Avatar asked Nov 23 '11 15:11

Nix


1 Answers

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.

like image 140
kindall Avatar answered Sep 29 '22 13:09

kindall