Open Spill-Tea opened 1 year ago
Here are a whole bunch of ways one can use a class object to hold state for the decorator. Should give you a few ideas.
Key thing is ensuring that introspection still works. You cannot use the same structure as your method 2 uses if you want introspection to yield the correct results. As is, the prototype of __call__
in your example would be what results when introspection is done.
import wrapt
import functools
import inspect
class CBD1:
def __init__(self, *params):
self._params = params
@wrapt.decorator
def __call__(self, wrapped, instance, args, kwargs):
try:
print("before", self._params)
return wrapped(*args, **kwargs)
finally:
print("after")
# Use same class to encapsulate a family of decorators.
@wrapt.decorator
def variant(self, wrapped, instance, args, kwargs):
try:
print("before", self._params)
return wrapped(*args, **kwargs)
finally:
print("after")
# Following could have been applied on __call__() if desired.
def variant_with_params(self, *params):
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
try:
print("before", self._params, params)
return wrapped(*args, **kwargs)
finally:
print("after")
return wrapper
def variant_with_optional_params(self, wrapped=None, *, param3=None, param4=None):
if wrapped is None:
return functools.partial(self.variant_with_optional_params,
param3=param3, param4=param4)
@wrapt.decorator
def wrapper(wrapped, instance, args, kwargs):
try:
print("before", self._params, (param3, param4))
return wrapped(*args, **kwargs)
finally:
print("after")
return wrapper(wrapped)
# Unique state object per decorated function.
@CBD1("param1", "param2")
def func1():
print("func1")
print("func1", inspect.signature(func1), sep="")
func1()
@CBD1("param1", "param2").variant
def func2():
print("func2")
print("func2", inspect.signature(func2), sep="")
func2()
@CBD1("param1", "param2").variant_with_params("param3", "param4")
def func3():
print("func3")
print("func3", inspect.signature(func3), sep="")
func3()
@CBD1("param1", "param2").variant_with_optional_params
def func4():
print("func4")
print("func4", inspect.signature(func4), sep="")
func4()
@CBD1("param1", "param2").variant_with_optional_params(param3="param3", param4="param4")
def func5():
print("func5")
print("func5", inspect.signature(func5), sep="")
func5()
# Shared state object for a group of decorated functions.
cbd1 = CBD1("param1", "param2")
@cbd1
def func6():
print("func6")
print("func6", inspect.signature(func6), sep="")
func6()
@cbd1.variant
def func7():
print("func7")
print("func7", inspect.signature(func7), sep="")
func7()
@cbd1.variant_with_params("param3", "param4")
def func8():
print("func8")
print("func8", inspect.signature(func8), sep="")
func8()
# Using functions to hide state object construction.
def cbd2(wrapped):
return CBD1()(wrapped)
@cbd2
def func9():
print("func9")
print("func9", inspect.signature(func9), sep="")
func9()
def cbd3(*params):
def wrapper(wrapped):
return CBD1(*params)(wrapped)
return wrapper
@cbd3("param1", "param2")
def func10():
print("func10")
print("func10", inspect.signature(func10), sep="")
func10()
def cbd4(*params):
def wrapper(wrapped):
return CBD1().variant_with_params(*params)(wrapped)
return wrapper
@cbd4("param3", "param4")
def func11():
print("func11")
print("func11", inspect.signature(func11), sep="")
func11()
# Using raw FunctionWrapper object proxy.
class CBD2(wrapt.FunctionWrapper):
def __init__(self, wrapped):
super().__init__(wrapped, self.wrapper)
def wrapper(self, wrapped, instance, args, kwargs):
try:
print("before")
return wrapped(*args, **kwargs)
finally:
print("after")
def cbd5(wrapped):
return CBD2(wrapped)
@cbd5
def func12():
print("func12")
print("func12", inspect.signature(func12), sep="")
func12()
Not that you must use try
/finally
pattern if want to run after action when wrapped function raises an exception.
It would be profoundly helpful to provide a walkthrough, demonstrating how to convert common existing decorator patterns, to use the wrapt package.
Take a simple case, of a decorator that measures the execution time of the function it wraps, which may be implemented as either a function or class decorator. For brevity, I reuse the functional decorator to reduce duplicated code. See the following three typical patterns:
and each of these decorators may be used as follows...
From the current documentation, it shows us how to create decorators in the first case "Method 1" - Function as a decorator, as follows:
however, It isn't entirely clear, how we convert classes as Decorators? From the documentation, we have the following, which is another function:
In these simple cases, we could easily convert our class based method decorators, into functions as shown above, but in more complicated examples (where we need to manage more states of the class), this may not be so easy. Is there a way to create class based wrappers using wrapt?