How do I make function decorators and chain them together?
Check out the documentation to see how decorators work. Here is what you asked for:
from functools import wraps
def makebold(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapper
def makeitalic(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</i>"
return wrapper
@makebold
@makeitalic
def hello():
return "hello world"
@makebold
@makeitalic
def log(s):
return s
print hello() # returns "<b><i>hello world</i></b>"
print hello.__name__ # with functools.wraps() this returns "hello"
print log('hello') # returns "<b><i>hello</i></b>"
How to use multiple decorator to a function and chain them together?
Your decorators method should
return
the result of the divisiondef check_zero_error(division_func):
def inner(a, b):
if b == 0:
return "Denominator can't be zero"
else:
return division_func(a, b)
return inner
def check_numerator(division_func):
def inner(a, b):
if a < b:
a, b = b, a
return division_func(a, b)
return innerChange the order of decorators, first
swap
(check_numerator
) then if needed, THEN check the denominator withcheck_zero_error
@check_numerator
@check_zero_error
def division(a, b):
return a / b
Giving expected
print(division(1, 2)) # actual output : 2.0
print(division(0, 2)) # actual output : "Denominator can't be zero"
print(division(4, 1)) # actual output : 4.0
print(division(5, 0)) # actual output : "Denominator can't be zero"
Chaining decorators
You should call the decorated function in the decorator's wrapper (each of them):
def decorator_ES(func_name):
def ES_decorator(func):
@wraps(func)
def ES_wrapper(*args):
return func(args[0]+ '!') # <-- HERE
return ES_wrapper
return ES_decorator
Same for another decorator.
Otherwise, you effectively replace the function with the latest (topmost) wrapper. So, when you call it, it never gets to the bottom decorator or to the function itself.
multiple python decorators
You'll need to swap the @decorator1
and @decorator2
lines if you want decorator2
to check up on whatever decorator1
returned:
@decorator2
@decorator1
def my_method(self, request, *args, **kwargs):
return u'The result that must be returned if all the checks performed by the decorator succeed'
Now decorator2
will wrap whatever method decorator1
returned, and you can thus inspect what that method returns.
def decorator2(method_to_decorate):
@wraps(method_to_decorate)
def wrapper2(self, request, *args, **kwargs):
result = method_to_decorate(self, request, *args, **kwargs)
if isinstance(result, tuple) and result and result[0] == 'failure':
# decorator1 returned a failure
return result
else:
# decorator1 passed through the wrapped method call
if decorator2_s_test_was_successful:
return result
else:
return ('another failure', 'message')
return wrapper2
How to apply the same decorator chain to multiple functions
You could define a new decorator which returns a pre-decorated function with the chain you want. For example, we can first define three custom decorators:
import functools
# A decorator factory which returns a new decorator.
def decorator_factory(message):
def decorator(function):
# Wraps the decorated function.
@functools.wraps(function)
def wrapper(*args, **kwargs):
# Example behavior:
# - Prints a message before calling the decorated function.
print(message)
# Calls the decorated function.
return function(*args, **kwargs)
return wrapper
return decorator
# Defines three new decorators.
decorator_1 = decorator_factory("Ham")
decorator_2 = decorator_factory("Spam")
decorator_3 = decorator_factory("Eggs")
The way these decorators are presently invoked resembles the following, which quickly becomes repetitive for multiple functions:
@decorator_1
@decorator_2
@decorator_3
def f():
pass # Do something.
@decorator_1
@decorator_2
@decorator_3
def g():
pass # Do something.
@decorator_1
@decorator_2
@decorator_3
def h():
pass # Do something.
However, you can decorate a wrapper function within the body of a decorator:
def decorator_chain(function):
@functools.wraps(function)
@decorator_1
@decorator_2
@decorator_3
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
Which simplifies the function definitions to:
@decorator_chain
def f():
pass # Do something.
@decorator_chain
def g():
pass # Do something.
@decorator_chain
def h():
pass # Do something.
In your provided example, this might look something like the following:
import functools
def decorator_chain(function):
@functools.wraps(function)
@extend_schema(
methods = ['GET'],
responses = {(200, STYLES_MIME_TYPE): OpenApiTypes.BINARY}
)
@extend_schema(
methods = ['PUT'],
request = {STYLES_MIME_TYPE: OpenApiTypes.BINARY},
responses = {
(204, 'application/json'): OpenApiResponse(
response = {'type': 'array', 'items': {'type': 'integer', 'format': 'int32'}},
examples = [
OpenApiExample(
'Returned style IDs example',
status_codes = ['204'],
value = [101, 102, 103]
)
]
)
}
)
@api_view(['GET', 'PUT'])
@permission_classes([IsAuthenticated | ReadOnly])
@renderer_classes([StylesRenderer, StylesJSONRenderer])
@parser_classes([StylesParser])
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
@decorator_chain
def styles(request: Request, pid: int) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(pk=pid)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
@decorator_chain
def styles_xref(request: Request, xref: uuid.UUID) -> Response:
"""
Get or save styles for a project.
GET - protobuf binary response
POST - returnd IDs for saved styles
"""
try:
project = Project.objects.get(xref=xref)
return _handle_styles(request, project)
except Project.DoesNotExist:
raise Http404()
Using a decorator factory could even allow you to quickly create different variants of a given chain of decorators.
How to create a function that can apply multiple decorators to another function?
Yes you could. You can make another decorator that returns the decorated function like so:all_decorators_for_foo.py
def all_decorators_for_foo(func):
return deco1()(
deco2('param')(
deco3(
'multiple',
'long',
'params'
)(
deco4('multiple', 'params')(
func
)
)
)
)
and then in file.py
from another_file import all_decorators_for_foo
@all_decorators_for_foo
def foo():
pass
Can I combine two decorators into a single one in Python?
A bit more general:
def composed(*decs):
def deco(f):
for dec in reversed(decs):
f = dec(f)
return f
return deco
Then
@composed(dec1, dec2)
def some(f):
pass
is equivalent to
@dec1
@dec2
def some(f):
pass
Decorated and wrapped function is not passing self to instance method
Fundamentally, the problem is that @backgroundThread()
doesn't wrap an instance method x.myfun
; it wraps the function X.myfun
that is namespaced to the class.
We can inspect the wrapped result:
>>> X.myfun
functools.partial(<function backgroundThread.<locals>.fnWrapper.<locals>.argsWrapper at 0x7f0a1e2e7a60>, '')
This is not usable as a method, because functools.partial
is not a descriptor:
>>> X.myfun.__get__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'functools.partial' object has no attribute '__get__'
>>> class Y:
... # no wrapper
... def myfun(self, *args, **kwargs):
... print(args, kwargs)
... print("myfun was called")
... #1 / 0
...
>>> Y.myfun.__get__
<method-wrapper '__get__' of function object at 0x7f0a1e2e7940>
Because X.myfun
is not usable as a descriptor, when it is looked up via x.myfun
, it is called like an ordinary function. self
does not receive the value of x
, but instead of the first argument that was passed, resulting in the wrong output for the (1, 2, foo='bar')
case and the exception for the ()
case.
Instead of having argsWrapper
accept a name
and then binding it with partial
, we can just use the name
from the closure - since we are already doing that with decorated_func
anyway. Thus:
def backgroundThread(name=''):
def fnWrapper(decorated_func):
def argsWrapper(*inner_args, **inner_kwargs):
nonlocal name
def exceptionWrapper(fn, *args, **kwargs):
try:
fn(*args, **kwargs)
except:
traceback.print_exc()
if not name:
name = decorated_func.__name__
th = Thread(
name=name,
target=exceptionWrapper,
args=(decorated_func, ) + inner_args,
kwargs=inner_kwargs
)
th.start()
return argsWrapper
return fnWrapper
Here, nonlocal name
is needed so that argsWrapper
has access to a name from a scope that is not the immediate closure, but also isn't global.
Related Topics
Why Is Reading Lines from Stdin Much Slower in C++ Than Python
Take a Screenshot Via a Python Script on Linux
Ensure a Single Instance of an Application in Linux
Understanding Python Subprocess.Check_Output'S First Argument and Shell=True
Call Python Code from an Existing Project Written in Swift
Does Python Have a Ternary Conditional Operator
How to List All Files of a Directory
Understanding Python Super() With _Init_() Methods
Why Does "Pip Install" Inside Python Raise a Syntaxerror
Configure Flask Dev Server to Be Visible Across the Network
Is There Go Up Line Character? (Opposite of \N)
How to Get Linux Console Window Width in Python
What Do I Use on Linux to Make a Python Program Executable
Python Script as Linux Service/Daemon
Hex/Binary String Conversion in Swift
"Is" Operator Behaves Unexpectedly With Integers
How to Profile a Python Script
How to Sort a List of Dictionaries by a Value of the Dictionary