Python/Decorators: Difference between revisions
From charlesreid1
(Created page with "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 o...") |
(Improve description: clearer structure, professional tone, Python 3 syntax, step-by-step explanation of the two-level decorator pattern (via update-page on MediaWiki MCP Server)) |
||
| Line 1: | Line 1: | ||
== Overview == | |||
This page demonstrates a two-level decorator pattern in Python: a '''function decorator''' (<code>time_this</code>) combined with a '''class decorator''' (<code>time_all_class_methods</code>) that transparently wraps every method of a class. The technique uses <code>__getattribute__</code> interception to make the wrapper behave like the original object while injecting timing instrumentation. | |||
'''Reference:''' Advanced decorator usage: https://www.codementor.io/sheena/advanced-use-python-decorators-class-function-du107nxsv | |||
== How It Works == | |||
=== <code>time_this</code> — 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. | |||
=== <code>time_all_class_methods</code> — 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 (<code>self.oInstance</code>). It overrides <code>__getattribute__</code> to intercept every attribute access: | |||
# '''First''', it tries to return the attribute from the wrapper itself (via <code>super().__getattribute__</code>). | |||
# '''If that fails''' with an <code>AttributeError</code>, it retrieves the attribute from the wrapped instance. | |||
# '''If the retrieved attribute is an instance method''', it wraps it with <code>time_this</code> 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 == | |||
<syntaxhighlight lang="python"> | |||
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): | def time_all_class_methods(Cls): | ||
"""Class decorator: wraps every instance method of Cls with time_this.""" | |||
class NewCls(object): | class NewCls(object): | ||
def __init__(self,*args,**kwargs): | def __init__(self, *args, **kwargs): | ||
self.oInstance = Cls(*args,**kwargs) | self.oInstance = Cls(*args, **kwargs) | ||
def __getattribute__(self,s): | |||
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). | |||
the attribute is an instance method | 3. If the attribute is an instance method, apply time_this. | ||
""" | """ | ||
try: | try: | ||
x = super(NewCls,self).__getattribute__(s) | x = super(NewCls, self).__getattribute__(s) | ||
except AttributeError: | except AttributeError: | ||
pass | pass | ||
else: | else: | ||
return x | return x | ||
x = self.oInstance.__getattribute__(s) | x = self.oInstance.__getattribute__(s) | ||
if type(x) == type(self.__init__): | |||
return time_this(x) | # Check if the attribute is an instance method | ||
if type(x) == type(self.__init__): | |||
return time_this(x) | |||
else: | else: | ||
return x | return x | ||
return NewCls | return NewCls | ||
# ---- Example usage ---- | |||
@time_all_class_methods | @time_all_class_methods | ||
class Foo(object): | class Foo(object): | ||
def a(self): | def a(self): | ||
print "entering a" | print("entering a") | ||
import time | import time | ||
time.sleep(3) | time.sleep(3) | ||
print "exiting a" | print("exiting a") | ||
oF = Foo() | oF = Foo() | ||
oF.a() | oF.a() | ||
</syntaxhighlight> | |||
== Key Points == | |||
</ | * The wrapper class is ''transparent'': accessing <code>oF.some_attr</code> behaves as if you were accessing the original <code>Foo</code> instance. | ||
* <code>time_this</code> 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. | |||
Revision as of 13:58, 16 June 2026
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://www.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.