In previous article about class-based decorators for classes I gave you example how one can create decorator.
Those decorators have similarities to inheritance between classes.
I will review pros and cons for class-inheritance and decorator(function/class based).
To The Point
Decorator example:
from timeit import default_timer as timer
class TimingDecorator(object):
def __init__(self, klas):
self.klas = klas
self.org_calculation1 = self.klas.calculation1
self.klas.calculation1 = self.calculation1
def __call__(self, arg=None):
return self.klas.__call__()
def calculation1(self, *args, **kwargs):
start = timer()
self.org_calculation1(self.klas, *args, **kwargs)
end = timer()
print(end - start)
@TimingDecorator
class MyFancyClassTest():
def calculation1(self, a, b):
from time import sleep
# i'm adding a sleep - as a test case if counter works.
sleep(2)
return (a+b*2)/(3*a)
fancy_object = MyFancyClassTest()
fancy_object.calculation1(50, 215)
Pros:
- you can make an extension for class-methods without inheritance
- you could mix inheritance with decorators
- you still have the same class-object.
Cons:
- all methods needs to be initialized and re-assigned at init
- for each method you need to create another method in decorator (not flexible enough)
- decorator is limited to this class methods - can not be reused at different class with different methods.
Function decorator example:
def counter_print(function_name):
def wrapper(*args, **kwargs):
start = timer()
output_of_function = function_name(*args, **kwargs)
end = timer()
print(end - start)
return output_of_function
return wrapper
class MyFancyClassTest():
@counter_print
def calculation1(self, a, b):
from time import sleep
# i'm adding a sleep - as a test case if counter works.
sleep(2)
return (a+b*2)/(3*a)
fancy_object = MyFancyClassTest()
fancy_object.calculation1(50, 215)
Pros:
- you still have the same class-object
- you could can mix inheritance with decorators
- you don't need to re-create assignment for predefined class-methods
- your decorator is not limited to only this specific class. It is re-usable.
Cons:
- Not advice to use this solution if you have a lot of code to make. It can get messy.
Inheritance example
class MyFancyClassTest():
def calculation1(self, a, b):
from time import sleep
# i'm adding a sleep - as a test case if counter works.
sleep(2)
return (a+b*2)/(3*a)
class TimerMyFancyClassTest(MyFancyClassTest):
def calculation1(self, *args, **kwargs):
start = timer()
output = super().calculation1(*args, **kwargs)
end = timer()
print(end - start)
return output
fancy_object = TimerMyFancyClassTest()
fancy_object.calculation1(50, 215)
Pros:
- your extended class does the same as decorator (prints timing for method)
Cons:
- You need to create code that extends main code
- It is not re-usable/adoptable to other class-methods
- you endup with different object and comparison is a bit more complex
MIX (inheritance at decorators class)
from timeit import default_timer as timer
class MetaClassDecorator(object):
""" A meta class decorator that forces creating methods from decorator-appended class."""
def __init__(self, klas):
self.klas = klas
for klass_method in self.get_klas_methods():
exec('self.org_'+klass_method+' = self.klas.'+klass_method)
exec('self.klas.'+klass_method+' = self.'+klass_method)
def get_klas_methods(self):
""" based on https://stackoverflow.com/a/34452 """
all_methods = [
method_name for method_name in dir(self.klas)
if callable(getattr(self.klas, method_name)) and method_name[0:2] != "__"
]
return all_methods
def __call__(self, arg=None):
return self.klas.__call__()
class FancyTestDecor(MetaClassDecorator):
""" This decorator forces 2 methods - fancy_method and test1 """
def fancy_method(self, *args, **kwargs):
start = timer()
self.org_fancy_method(self.klas, *args, **kwargs)
end = timer()
print(end - start)
def test1(self, *args, **kwargs):
start = timer()
self.org_test1(self.klas, *args, **kwargs)
end = timer()
print(end - start)
@FancyTestDecor
class MyFancyClassTest():
def fancy_method(self):
print("fancy print from fancy method")
def test1(self):
print("Ttest11")
fancy_object = MyFancyClassTest()
fancy_object.fancy_method()
fancy_object.test1()
Pros:
- you re-use pre-existing architecture for decorator.
- you create only class with methods (reusing meta-decorator-class)
- you can create a more complex solutions as decorators for classes.
Cons:
- All methods from
MyFancyClassTest
if any found, needs to be added and re-assigned to fields, because ofexec
for all methods that does not start with "__" private functions/methods - check docs for 2.7 python. - You create a non-reusable code just as in class-based decorator (unless you find other class that will have only those methods) (unless you find solution to replace the
exec
problem and re-assign methods without manually changing them at__init__
)
Snippets
class MetaClassDecorator(object):
""" A meta class decorator that forces creating methods from decorator-appended class."""
def __init__(self, klas):
self.klas = klas
for klass_method in self.get_klas_methods():
exec('self.org_'+klass_method+' = self.klas.'+klass_method)
exec('self.klas.'+klass_method+' = self.'+klass_method)
def get_klas_methods(self):
""" based on https://stackoverflow.com/a/34452 """
all_methods = [
method_name for method_name in dir(self.klas)
if callable(getattr(self.klas, method_name)) and method_name[0:2] != "__"
]
return all_methods
def __call__(self, arg=None):
return self.klas.__call__()
class FancyTestDecor(MetaClassDecorator):
""" This decorator counts timing of fancy_method method """
def fancy_method(self, *args, **kwargs):
start = timer()
self.org_fancy_method(self.klas, *args, **kwargs)
end = timer()
print(end - start)
Acknowledgements
Auto-promotion
Related links
- Eval scope in python2 vs python3
- Build-in Functions - python 3.6.4
- Introspection - Finding what methods a Python object has
- TimeIt - Measure execution time of small code
- performance - Measure time elapsed in Python?
Thanks!
That's it :) Comment, share or don't :)
If you have any suggestions what I should blog about in the next articles - please give me a hint :)
See you in the next episode! Cheers!
Comments
comments powered by Disqus