deff1():print("called f1")f1>><function f1 at 0x008EEDF8>
f1 is actually an object at some memory address in Python. So we can actually pass functions around (since they are objects) into functions, store it in variables, etc.
deff1():print("called f1")deff2(f):f()f2(f1)>> called f1
Example
defmy_decorator(func):defwrapper():print("Before")func()print("After")return wrapperdefsay_whee():print("Whee!")# This is function aliasingsay_whee =my_decorator(say_whee)'''>> say_whee()BeforeWhee!After'''## Equivalent to above - decorators make function aliasing nicer@my_decoratordefsay_whee():print("Whee!")'''>> say_whee()BeforeWhee!After'''
To remember easier, remember decorator is a function that wraps a function and takes in the function below it as an argument.
Decorating Functions with Arguments
defmy_decorator(func):defwrapper(*args,**kwargs):print("Before")func(*args, **kwargs)print("After")return wrapper@my_decoratordefsay_whee(a): # now that we have a parameter here, wrapper() and func() need to have inputs as well, so we use args and kwargsprint(a)say_whee("Whee!")
We use args and kwargs because we have no idea what arguments will be passed into the function. So this allows us to have any arguments or number of arguments in the function.
Returning values from decorators
defmy_decorator(func):defwrapper(*args,**kwargs):print("Before") val =func(*args, **kwargs)print("After")return valreturn wrapper@my_decoratordefadd(x,y):return x + y>>print(add(4,5))BeforeAfter9
defdecoratorFunctionWithArguments(arg1,arg2,arg3):defwrap(f):print"Inside wrap()"defwrapped_f(*args):print"Inside wrapped_f()"print"Decorator arguments:", arg1, arg2, arg3f(*args)print"After f(*args)"return wrapped_freturn wrap@decoratorFunctionWithArguments("hello", "world", 42)defsayHello(a1,a2,a3,a4):print'sayHello arguments:', a1, a2, a3, a4print"After decoration"print"Preparing to call sayHello()"sayHello("say", "hello", "argument", "list")print"after first sayHello() call"sayHello("a", "different", "set of", "arguments")print"after second sayHello() call"Here's the output:Inside wrap()After decorationPreparing to call sayHello()Inside wrapped_f()Decorator arguments: hello world 42sayHello arguments: say hello argument listAfter f(*args)after first sayHello() callInside wrapped_f()Decorator arguments: hello world 42sayHello arguments: a different set of argumentsAfter f(*args)after second sayHello() call
Making a class a custom decorator
See built in methods: __call__ and functools: wraps for prerequisite knowledge.
from functools import wrapsclassDecoratorClass:def__init__(self,a): self.a = a self.b = bdef__call__(self,func):@wraps(func)defwrapper(c):print(c) result =func(self)# Usage@DecoratorClass(1,2)# Remember you are calling __call__, not __init__ heredeff(c): ...
A decorator can also be called as a function. @decorator is equivalent to decorator(func) and @decorator(args) to decorator(args)(func). So you could return the value of those function returns conditionally in your decorator. Here is an example below: