Python: Decorator
What is Decorator?
Python “decorator” is a language construct that lets you define a wrapper function g to another function f, such that, when f is called, your wrapper g is called instead. (it can also be applied to methods, and Class .)
The syntax for “decorator” is @name1 immediately before the line of def name2.
Here's a decorator:
# define function g here @g def f(): print("xyz")
is equivalent to
def f(): print("xyz") f = g(f)
That is ALL there is of Python decorators.
Note that:
- when the function
fis called, the wrappergwill be called instead. - the wrapper function
greceivesfas its argument. - the wrapper function
gmust return a function. Because, remember, the value offis nowg(f), andfis a function. So, whenfis called such asf(3), Python evaluatesg(f)(3), sog(f)must be a function too. (technically, it just need to be anything callable.)
Here's a example of decorator:
# example of a decorator def gg(xx): print("gg called") def hh(y): return y - 1 return hh @gg # this is decorator def ff(x): print("ff called") return x + 1 print(ff(3)) # prints # gg called # return # 2
Note: the wrapper function gg receives the function ff. The wrapper can do anything with it. Typically, it'll eventually call ff, but it doesn't have to.
In the above example, the wrapper gg bypass ff entirely.
Why Use Decorator
typically, decorators are use to add class methods or static methods, or setting pre-conditions, synchronisation.
Checklist for Writing a Decorator
Here's 4 things to remember when writing a decorator:
- The wrapper function
gmust take a function as argument. (because it'll receivefas argument.) - The wrapper function
gmust return a function (e.g.h). (because callingfresults in calling return value ofg.) - The returned function
hmust take the same number and type of arguments asf. (Tip: usedef h(*args, **keywords)to catch all.) - OFTEN, the returned function
hshould return the same type of value thatfreturns. Iffreturns a string,hprobably should too. IffreturnsNone,hprobably should too.
Decorator Examples
Decorator Example: Check or Modify Argument
Here's a example of decorator. It (in effect) modifies the function's argument.
# example of a decorator def gg(func): """a decorator for ff. Make sure input to ff is always even. If not, add 1 to make it even""" def hh(y): if y % 2 == 0: # even return func(y) else: return func(y + 1) return hh @gg def ff(x): return x print(ff(3)) # 4 print(ff(4)) # 4 print(ff(5)) # 6 print(ff(6)) # 6
Note: decorator function gg does not know what the decorated function ff's arguments. However, since gg is a wrapper, it can create and return a new function hh, and have hh check arguments then call ff(args). This works because whatever arguments passed to the original function ff is passed to hh, then hh can do arg checking, then call ff.
Decorator Example: Catch All Arguments (And Run Conditionally)
When defining the decorator function, often you need to catch all possible arguments of the original function. Here's a example of how.
In the following example, the original function is called only if some global variable is true.
# example of decorator that check condition to decide whether to call # the condition cc = True def gg(func): """a decorator for ff. If cc is true, run ff, else do nothing""" def hh(*args, **xkeywords): # param must catch all args of ff """do nothing function""" pass if cc: return func else: return hh @gg def ff(*args, **xkeywords): # ff can be sending email, or any callable with no return value print("ff") pass ff(3) ff(3, "Jane") ff(3, 4, k="thank you") # if cc is true, then ff is called 3 times # (different set of arguments are used, to illustrate that our wrapper work well with them) # if cc is false, Nothing's done, ff is not called.
〔see Python: Function〕
Decorator with Parameters
Decorator itself can have parameters.
For example, the following decorator:
@g(3) def f(): print("xyz")
is equivalent to
def f(): print("xyz") f = g(3)(f)
There's nothing special about this. In the simplest decorator example, @g followed by def f: gets transformed into f = g(f). Now, if we replace g by g(x), we get f = g(x)(f).
Decorators can get complex, because it's transforming code and doing function applications and involves nested functions. In this case, just remember that:
g(x)must return a function, let's call ith1. This will receivefas argument.h1must take a function. (h1will receivefas argument.)h1must return a function (let's call ith2), becauseh2will receivef's argument(s).
Example:
# example of decorator with argument def gg(num): """a decorator for ff. Ignore ff. Simply return num.""" def h1(f1): def h2(f2): return num return h2 return h1 # gg(1) must return a function h1, such that h1 accept function (the ff), and also return a function (to be applied to ff's args) @gg(1) def ff(x): return repr(x) + " rabbits" print(ff(3)) # 1 print(ff(4)) # 1 print(ff(5)) # 1
Example: Decorator as a Class
# example of decorator on class class Dog: def __init__(self, hh): print("Dog.__init__() called") hh() # Prove that function definition has completed def __call__(self): print("Dog.__call__() called") @Dog # this forces Dog to be called when ff is called def ff(): print("ff() called") ff() # Dog.__init__() called # ff() called # Dog.__call__() called
Example 2
class Gate(object): def __init__(self, f): self.f = f def __call__(self): print("enter", self.f.__name__) self.f() print("exit", self.f.__name__) @Gate def f1(): print("f1() called") @Gate def f2(): print("f2() called") f1() f2() # enter f1 # f1() called # exit f1 # enter f2 # f2() called # exit f2
Reference
- http://www.python.org/dev/peps/pep-0318/
- http://wiki.python.org/moin/PythonDecorators
- Decorator pattern
- Decorators I: Introduction to Python Decorators
- By Bruce Eckel.
- http://www.artima.com/weblogs/viewpost.jsp?thread=240808
- Understanding Python Decorators in 12 Easy Steps!
- By Simeon Franklin.
- http://simeonfranklin.com/blog/2012/jul/1/python-decorators-in-12-steps/