From charlesreid1

Advanced decorator usage: https://www.codementor.io/sheena/advanced-use-python-decorators-class-function-du107nxsv

Pretty mind-blowing use of a CLASS decorator, which takes one type of class in and returns another type of class out, and wraps the original class with a new one. This uses some advanced features of python classes as well to provide a transparent object wrapper.

In this case, when the user accesses attributes of the wrapper class, it first tries to return that attribute from the wrapper class; if that fails, it tries to return that attribute from the wrapped object. If the attribute is a function, it wraps that function handle with a decorator.

In effect, this wraps every single method implemented in the wrapped object, and decorates it with a timing method - which saves the user from having to copypaste a bunch of decorators (or worse, copypasta a bunch of code!).

def time_this(original_function):      
    print "decorating"                      
    def new_function(*args,**kwargs):
        print "starting timer"       
        import datetime                 
        before = datetime.datetime.now()                     
        x = original_function(*args,**kwargs)                
        after = datetime.datetime.now()                      
        print "Elapsed Time = {0}".format(after-before)      
        return x                                             
    return new_function  

def time_all_class_methods(Cls):
    class NewCls(object):
        def __init__(self,*args,**kwargs):
            self.oInstance = Cls(*args,**kwargs)
        def __getattribute__(self,s):
            """
            this is called whenever any attribute of a NewCls object is accessed. This function first tries to 
            get the attribute off NewCls. If it fails then it tries to fetch the attribute from self.oInstance (an
            instance of the decorated class). If it manages to fetch the attribute from self.oInstance, and 
            the attribute is an instance method then `time_this` is applied.
            """
            try:    
                x = super(NewCls,self).__getattribute__(s)
            except AttributeError:      
                pass
            else:
                return x
            x = self.oInstance.__getattribute__(s)
            if type(x) == type(self.__init__): # it is an instance method
                return time_this(x)                 # this is equivalent of just decorating the method with time_this
            else:
                return x
    return NewCls

#now lets make a dummy class to test it out on:

@time_all_class_methods
class Foo(object):
    def a(self):
        print "entering a"
        import time
        time.sleep(3)
        print "exiting a"

oF = Foo()
oF.a()