From charlesreid1


Overview

This page demonstrates a two-level decorator pattern in Python: a function decorator (time_this) combined with a class decorator (time_all_class_methods) that transparently wraps every method of a class. The technique uses __getattribute__ interception to make the wrapper behave like the original object while injecting timing instrumentation.

Reference: Advanced decorator usage: https://web.archive.org/web/20201112033055/https://codementor.io/@sheena/advanced-use-python-decorators-class-function-du107nxsv

How It Works

time_this — Function-level decorator

A standard decorator that wraps a function with timing logic: it records the wall-clock time before and after the call, prints the elapsed time, and returns the original result unchanged.

time_all_class_methods — Class-level decorator

This decorator takes a class and returns a new wrapper class. When instantiated, the wrapper creates an internal instance of the original class (self.oInstance). It overrides __getattribute__ to intercept every attribute access:

  1. First, it tries to return the attribute from the wrapper itself (via super().__getattribute__).
  2. If that fails with an AttributeError, it retrieves the attribute from the wrapped instance.
  3. If the retrieved attribute is an instance method, it wraps it with time_this before returning it.

This means every method call on the wrapper is automatically timed — the caller does not need to decorate each method individually.

Code

def time_this(original_function):
    """Decorator: prints the wall-clock time spent in the decorated 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 decorator: wraps every instance method of Cls with time_this."""
    class NewCls(object):
        def __init__(self, *args, **kwargs):
            self.oInstance = Cls(*args, **kwargs)

        def __getattribute__(self, s):
            """
            Intercept every attribute access on the wrapper.
            1. Try the wrapper's own attributes first.
            2. Fall back to the wrapped instance (self.oInstance).
            3. If the attribute is an instance method, apply time_this.
            """
            try:
                x = super(NewCls, self).__getattribute__(s)
            except AttributeError:
                pass
            else:
                return x

            x = self.oInstance.__getattribute__(s)

            # Check if the attribute is an instance method
            if type(x) == type(self.__init__):
                return time_this(x)
            else:
                return x

    return NewCls


# ---- Example usage ----
@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()

Key Points

  • The wrapper class is transparent: accessing oF.some_attr behaves as if you were accessing the original Foo instance.
  • time_this is applied lazily — only when a method is accessed, not at wrapper-creation time.
  • This pattern is useful for cross-cutting concerns like logging, profiling, access control, or memoization that should apply to every method of a class without repetitive per-method decorator boilerplate.





See also: