How to Make Function Decorators and Chain Them Together

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?

  1. Your decorators method should return the result of the division

    def 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 inner
  2. Change the order of decorators, first swap (check_numerator) then if needed, THEN check the denominator with check_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



Leave a reply



Submit