pydata

Keep Looking, Don't Settle

python decorators

0. python functions

everything is python is an object.

1: assign function to a variable

def print_it(msg):
    print msg

function1 = print_it
function1("hellow")

2: define function inside another function

def hellow(name):
    def msg():
        return "hellow "  
    return msg() + name

print hellow("sir")

3: function can be passed as parameters to another function(hign order funciton)

def hellow(name):
    return "hellow" + name

def call_func(func):
    time = " 2016"
    return func(time)

print call_func(hellow)
1. python decorators

decorator is to add functionality to an existing code. it will modify another part of program at compile time. in fact, decorator is a higher order function which return a function.

let's look at a piece of code to record the runtime:

import timeit
import numpy as np

def time_record(func):
    start = timeit.default_timer()
    result = func()
    end = timeit.default_timer()
    print "run time is " + str(end - start)
    return result

def sin_cos():
    s = np.random.random(100)
    return np.sin(s) + np.cos(s)

time_record(sin_cos)
# run time is 3.89412213906e-05

这里time_record是一个高阶函数,它的参数是另一个函数sin_cos。它起的作用是在sin_cos运行的时候,计算所用的时间。或者从另一个角度看,对sin_cos起了一个简单的装饰作用。这就是decorator的含义。

如果要写成decorator的样子,我们可以重新写为

import timeit
import numpy as np

def time_record(func):
    start = timeit.default_timer()
    result = func()
    end = timeit.default_timer()
    print "run time is " + str(end - start)
    return result

@time_record
def sin_cos():
    s = np.random.random(100)
    return np.sin(s) + np.cos(s)

sin_cos
# run time is 5.46384578684e-05

这个时候就不需要再把sin_cos作为参数传递给time_record了,因为@time_record已经做了这件事

2. decorator function has parameter

如果decorator函数有参数,比如decorator(params)(inner_function),我们可以做同样的处理:

def time_record(name):
    def decorator(func):
        start = timeit.default_timer()
        result = func()
        end = timeit.default_timer()
        print "Hellow " + name + ", run time is " + str(end - start)
        return result
    return decorator

@time_record("python-programming")
def sin_cos():
    s = np.random.random(100)
    return np.sin(s) + np.cos(s)    

sin_cos  
# Hellow python-programming, run time is 6.12795965935e-05  
3. both decorator function and inner function have parameters

同样,如果装饰函数和目标函数都有参数,这个时候要多加一层:

def time_record(name):
    def decorator(func):
        def wrapper(*args, **kvargs):
            start = timeit.default_timer()
            result = func(*args, **kvargs)
            end = timeit.default_timer()
            print "current run function is %s" %(func.__name__)
            print "Hellow " + name + ", run time is " + str(end - start)
            return result
        return wrapper
    return decorator

@time_record("python-programming")
def my_sin(x):
    return np.sin(x)

my_sin(np.random.random(100))  
# current run function is sin
# Hellow python-programming, run time is 1.47916268816e-05

这样看起来没有什么问题,但是如果我们现在检查被decorator装饰过的my_sin__name__,我们会发现它的名字变成了wrapper。

print my_sin.__name__
# wrapper

我们需要调用Python内置的functools.wraps

def time_record(name):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kvargs):
            start = timeit.default_timer()
            result = func(*args, **kvargs)
            end = timeit.default_timer()
            print "current run function is %s" %(func.__name__)
            print "Hellow " + name + ", run time is " + str(end - start)
            return result
        return wrapper
    return decorator

@time_record("python-programming")
def my_sin(x):
    return np.sin(x)

my_sin(np.random.random(100))  

这时候再检查sin__name__,就没有问题了:

print my_sin.__name__
# my_sin
4. 多层decorator

我们经常干这种事情,在输出的前后分别加上30个*和30个%. 我们可以用decorator方便的实现这个功能:

def add_star(func):
    def inner(*args, **kvargs):
        print '*'*30
        result = func(*args, **kvargs)
        print '*'*30
    return inner

def add_pct(func):
    def inner(*args, **kvargs):
        print '%'*30
        result = func(*args, **kvargs)
        print '%'*30
    return inner

@add_star
@add_pct
def my_sin(x):
    print np.sin(x)

my_sin(np.random.random(2))  
# ******************************
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# [ 0.48007195  0.32247404]
# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
# ******************************

Reference

  1. 装饰器
  2. Python Decorators