A Python decorator, similar with Decorator Pattern, is a specific change to the Python syntax that allows us to more conveniently alter functions and methods. In this post, I will try my best to deep into this advanced feature in Python.
The winning syntax as of now uses the @ symbol
@classmethod
def foo():
# do something
First-class Function Link to heading
It’s simple to use but difficult to penetrate its fundamental. Before diving, what must be clear is that function is the first-class citizen, which can be dynamically created, destroyed, passed to a function, returned as a value, and have all the rights as other variables in the programming language have.
def say_hello(name):
return "hello to " + name
# 1. assign function to a variable
say_hello_to = say_hello
print say_hello_to('John') # hello to John
# 2. pass function as parameter to other function
def call_func(func, name):
return func(name)
print call_func(say_hello, 'John') # hello to John
# 3. return a function in other function
def compose_func():
def name():
return 'John'
return name
print compose_func() # <function name at 0x10bcdccf8>
hello = compose_func()
print hello() # John
'''
4. inner function has access to the enclosing scope
notice how to read a "name" argument from the enclosing scope of the inner function and return the new function.
'''
def compose_func(name):
def complete_msg():
return 'hello to ' + name
return complete_msg
hello = compose_func('John')
print greet() # hello to John
Rough Decorator Implemented by Function Link to heading
The combination of attributes above will help us build a decorator. In this example, let’s simply consider a function that wraps the string output to uppercase.
def get_name(name):
return name
def p_decorate(func):
def wrapper(name):
return name.upper()
return wrapper
my_name = p_decorate(get_name)
print my_name('John') # JOHN
Python’s Decorator Syntax Link to heading
Python provides a syntactic sugar, a @ symbol, to decorate function easily, replacing my_name = p_decorate(get_name) above.
def p_decorate(func):
def wrapper(name):
return name.upper()
return wrapper
@p_decorate
def get_name(name):
return name
print my_name('John') # JOHN
Decorating Method Link to heading
Same to function, method can also be wrapped by decorators. Whereas, the first parameter of wrapper function must be reference to the current object.
def p_decorate(func):
def wrapper(self):
return func(self).upper()
return wrapper
class Player(object):
def __init__(self):
self.name = 'Chris'
self.family = 'Paul'
@p_decorate
def get_fullname(self):
return self.name + " " + self.family
cp3 = Player()
print cp3.get_fullname() # CHRIS PAUL
A much better approach would be to make our decorator useful for functions and methods alike. This can be done by putting args and *kwargs as parameters for the wrapper, then it can accept any arbitrary number of arguments and keyword arguments.
def p_decorate(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs).upper()
return wrapper
class Player(object):
def __init__(self):
self.name = 'Chris'
self.family = 'Paul'
@p_decorate
def get_fullname(self):
return self.name + " " + self.family
cp3 = Player()
print cp3.get_fullname() # CHRIS PAUL
Function Object Link to heading
We can also construct a function object using class, with a __call__ member function inside.
class FuncObj(object):
def __init__(self, name):
print 'init'
self.name = name
def __call__(self):
print 'hello', self.name
fo = FuncObj('chris') # init
fo() # hello chris
From the example above, we infer that function calling can be decomposed to two steps: construct and call. Base on this, a decorator implemented by class is feasible.
Decorator Implemented by Class Link to heading
class make_upper(object):
def __init__(self, func):
print 'init'
self.func = func
def __call__(self):
print 'call'
print self.func().upper()
# init
@make_upper
def get_name():
return 'chris'
# call
# CHRIS
get_name()
The code above is much alike with the following without @ syntax:
# call __init__
name = make_upper(get_name)
# call
# CHRIS
name()
Reference