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:
@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
f
is called, the wrapperg
will be called instead. - the wrapper function
g
receivesf
as its argument. - the wrapper function
g
must return a function. Because, remember, the value off
is nowg(f)
, andf
is a function. So, whenf
is 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:
# -*- coding: utf-8 -*- # python 2 # example of a decorator def gg(xx): print "gg called" def s(y): return y-1 return s @gg # ← this is decorator def ff(x): print "ff called" return x+1 print ff(3) # output: # gg called # 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.
Checklist for Writing a Decorator
Here's 4 things to remember when writing a decorator:
- The wrapper function
g
must take a function as argument. (because it'll receivef
as argument.) - The wrapper function
g
must return a function. (because that'll be applied tof
's arguments.) - The returned function
h
must take the same number/type of arguments asf
. - OFTEN, the returned function
h
should return the same type of value thatf
returns. Iff
returns a string,h
probably should too. Iff
returnsNone
,h
probably should too.
Decorator Examples
Decorator Example: Check/Modify Input Argument
Here's a example of decorator. It (in effect) modifies the function's argument.
# -*- coding: utf-8 -*- # python 2 # 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.
# -*- coding: utf-8 -*- # python 2 # example of decorator that check condition to decide whether to call cc = True # ← the condition def gg(func): """a decorator for ff. If cc is true, run ff, else do nothing""" def hh(*args, **kwargs): # ← param must catch all args of ff """do nothing function""" pass if cc: return func else: return hh @gg def ff(*args, **kwargs): # 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 receivef
as argument.h1
must take a function. (h1
will receivef
as argument.)h1
must return a function (let's call ith2
), becauseh2
will receivef
's argument(s).
Example:
# -*- coding: utf-8 -*- # python 2 # 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