Python/Decorators
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:
- First, it tries to return the attribute from the wrapper itself (via
super().__getattribute__). - If that fails with an
AttributeError, it retrieves the attribute from the wrapped instance. - If the retrieved attribute is an instance method, it wraps it with
time_thisbefore 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_attrbehaves as if you were accessing the originalFooinstance. time_thisis 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.
| Computer Science notes on computer science topics on the wiki, for educational and learning purposes
Part of the 2017 CS Study Plan.
Python/Exceptions · Python/Assertions · Python/Decorators Python/Os (os module) · Python/Strings Python/Splat · Python/Iterators · Python/Generators Python/Comparators · Python/Lambdas
Builtin features of Java: Java/Exceptions · Java/Assertions · Java/Memory · Java/Interfaces Java/Generics · Java/Decorators · Java/Diamond Notation Java/Iterators · Java/Iterable · Iterators vs Iterable Java/Comparators · Java/Comparable · Comparators vs Comparable Java/Numeric · Java/TypeChecking · Java/Testing · Java/Timing · Java/Profiling Documentation: Javadocs · Java/Documentation Tools and functionality: Java/URLs · Java/CSV External libraries: Guava · Fastutil · Eclipse Collections OOP: OOP Checklist · Java/Abstract Class · Java/Encapsulation · Java/Generics
|
See also: