【Technique】Python装饰器

1.基础知识

    python中函数也是对象,是可以被引用的对象,当然可以当作参数传递。如果想要给一个函数调用加上比如计时功能,用来对函数执行时间进行性能测试,比如想对冒泡排序算法进行性能分析,利用这个特点可如下实现:

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import time
import random

def AddTimeWatch(func):
    def _deco(seq):
        start=time.clock()
        func(seq)
        print "Time cost",time.clock()-start,"seconds"
    return _deco
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
BubbleSort=AddTimeWatch(BubbleSort)
                
count=100
seq=[]    
for i in range(count):
    seq.append(random.random()*100)

BubbleSort(seq)
print '*'*50

2.装饰器的存在简化了操作

    装饰器就是一个可以传入另外一个函数对象,并对其进行修改后返回产生的新的对象,旧对象将不再存在,其名称被新的对象使用。如下所示:

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import time
import random

def TimeWatch(func):
    def _deco(seq):
        start=time.clock()
        func(seq)
        print "Time cost",time.clock()-start,"seconds"
    return _deco
    
    
@TimeWatch
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
                
count=100
seq=[]    
for i in range(count):
    seq.append(random.random()*100)

BubbleSort(seq)
print '*'*50


输出结果:Time cost 0.00259057408446 seconds

                   **************************************************

3.装饰器也可以叠加使用

    比如想在测试性能的基础上添加简单的日志功能,通知什么时候被调用,什么时候结束调用:

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import time
import random

def TimeWatch(func):
    def _deco(seq):
        start=time.clock()
        func(seq)
        print "Time cost",time.clock()-start,"seconds"
    return _deco
    
def SysLog(func):
    def _wrapper(seq):
        print func.__name__," called at",time.ctime()
        func(seq)
        print func.__name__,"terminated at",time.ctime()
    return _wrapper
    
@SysLog    
@TimeWatch
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
                
count=100
seq=[]    
for i in range(count):
    seq.append(random.random()*100)

BubbleSort(seq)
print '*'*50

        但是有一个问题出现了,输出结果的形式是:

_deco  called at Mon Dec 17 20:46:51 2012
Time cost 0.00259057408446 seconds
_deco terminated at Mon Dec 17 20:46:51 2012
**************************************************

        也就是调用者函数的名称被改变了,不能反映真实的目的。解决的方法有两种:在装饰器函数内部改变其__name__属性为func__name__,这种方法比较简单,但是没有第二种方法强大,即python在functools模块中提供的wraps方法,它不仅可以保持原来对象的__name__,还保留了很多原对象的属性。

代码如下:

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import time
import random
import functools

def TimeWatch(func):
    @functools.wraps(func)
    def _deco(seq):
        start=time.clock()
        func(seq)
        print "Time cost",time.clock()-start,"seconds"
    return _deco
    
def SysLog(func):
    @functools.wraps(func)
    def _wrapper(seq):
        print func.__name__," called at",time.ctime()
        func(seq)
        print func.__name__,"terminated at",time.ctime()
    return _wrapper
    
@SysLog    
@TimeWatch
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
                
count=100
seq=[]    
for i in range(count):
    seq.append(random.random()*100)

BubbleSort(seq)
print '*'*50

输出结果符合预期:

BubbleSort  called at Mon Dec 17 20:57:40 2012
Time cost 0.00230751455191 seconds
BubbleSort terminated at Mon Dec 17 20:57:40 2012
**************************************************

4.带参数的装饰器

        形式:@decomaker(deco_args)

        作用:带参数的装饰器,需要自己返回以函数作为参数的装饰器。

        搜了些资料,看的比较晕,据我现在的理解是,要对一个函数进行装饰,那么我当然可以直接装饰,比如上面的加入计时器或者加入日志功能。但是如果我的装饰器想要同时依靠另外一些辅助函数作为装饰器的可调用对象,并且如果要想使用这个带参数的装饰器,那么你就必须为我提供一个带有这些辅助功能的参数,有如下等式:

俗语:带参数的装饰器(参数)=合格的装饰器;之后再用这个合格的装饰器函数去装饰(我去~说的好俗,鄙视自己)。

        另一种表达方式:

 @decomaker(deco_args)
 def foo():pass

        就等于decomaker(deco_args)(foo),这里的decomaker(foo)就是能用的装饰器,为什么能用,因为参数deco_args为这个decomaker提供了他内部调用的方法。

        所以辅助的功能可以定制。用更俗的方式并且不太恰当的解释,我是一个装修工人,要为一个家制作家具来装饰这个家。那么我就必须要有辅助的材料,材料可以是木头,可以是玻璃等等。这个材料就是装饰器要用到的参数。它们规定必须实现了一些方法,比如supply,因为在装饰器中会调用到这些方法。可以想到如果要提供这种形式的作用,可以用类对象封装很多staticmethod方法供调用。这里类起到了封装函数的作用。

        按照本文上面的例子,我们把系统日志改成带参数的装饰器,假设日志功能需要一个能够提供时间,但格式不定的参数,用来记录开始执行的时间和结束执行的时间。所以假定需要提供gettime函数。

代码如下:

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import time
import random
import copy


class TimeSupplier1():
    @staticmethod
    def gettime():
        return time.ctime()
class TimeSupplier2():
    @staticmethod
    def gettime():
        t=time.gmtime()
        return '%s年%s月%s日——%s点%s分%s秒'  %(t.tm_year,t.tm_mon,t.tm_mday,t.tm_hour,t.tm_min,t.tm_sec)
    

def SysLog(arg):
    def _Decorator(func):
        def _wrapper(*args,**kwargs):
            print func.__name__," called at",arg.gettime()
            func(*args,**kwargs)
            print func.__name__,"terminated at",arg.gettime()
        return _wrapper
    return _Decorator
    


@SysLog(TimeSupplier1)
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
                
@SysLog(TimeSupplier2)
def InsertSort(seq):
    n  = len(seq)
    for i in range(1,n):
        num=seq[i]
        j=i-1
        while (seq[j]>num and j>=0):
            seq[j+1]=seq[j]
            j=j-1
        seq[j+1]=num


count=1000
seq=[]    
for i in range(count):
    seq.append(random.random()*100)

seq_temp=copy.deepcopy(seq)
BubbleSort(seq_temp)
print '*'*50

seq_temp=copy.deepcopy(seq)
InsertSort(seq_temp)
print '*'*50

结果如下:

BubbleSort  called at Mon Dec 17 22:15:54 2012
BubbleSort terminated at Mon Dec 17 22:15:54 2012
**************************************************
InsertSort  called at 2012年12月17日——14点15分54秒
InsertSort terminated at 2012年12月17日——14点15分54
**************************************************

5.经常用的

@staticmethod

@classmethod

@property

@propertyname.setter

        另外插一句,staticmethod并不像C++里的static成员函数,反而classmethod像C++里的static成员函数。

6.我的总程序

        其实一开始是想对冒泡、插入和希尔排序搞的对比,练练python操作的。因为博客里用C++实现过了。现在看的看的跑这来了~~

# -*- coding: utf-8 -*-
"""
@author: akon
"""
import copy
import time
import random
import functools

def TimeWatch(func):
    @functools.wraps(func)
    def _deco(seq):
        start=time.clock()
        func(seq)
        print "Time cost",time.clock()-start,"seconds"
    return _deco

def SysLog(func):
    @functools.wraps(func)
    def _wrapper(seq):
        print func.__name__," called at",time.ctime()
        func(seq)
        print func.__name__,"terminated at",time.ctime()
    return _wrapper

@SysLog
@TimeWatch
def BubbleSort(seq):
    n=len(seq)
    for i in range(n-1):
        for j in range(n-1-i):
            if(seq[j]>seq[j+1]):
                seq[j],seq[j+1]=seq[j+1],seq[j]
@SysLog
@TimeWatch
def InsertSort(seq):
    n  = len(seq)
    for i in range(1,n):
        num=seq[i]
        j=i-1
        while (seq[j]>num and j>=0):
            seq[j+1]=seq[j]
            j=j-1
        seq[j+1]=num
@SysLog
@TimeWatch
def ShellSort(seq):
    N=len(seq)
    h=1
    while(h<N/3):
        h=3*h+1
    while(h>=1):
        for i in range(h,N):
            j=i
            while(seq[j]<seq[j-h] and j>=h):
                seq[j],seq[j-h]=seq[j-h],seq[j]
                j=j-h
        h=h/3
if __name__=='__main__':
    count=1000
    seq=[]    
    for i in range(count):
        seq.append(random.random()*100)
    
    seq_temp=copy.deepcopy(seq)
    BubbleSort(seq_temp)
    print '*'*50
    
    seq_temp=copy.deepcopy(seq)
    InsertSort(seq_temp)
    print '*'*50
    
    seq_temp=copy.deepcopy(seq)
    ShellSort(seq_temp)
    print '*'*50

7.可能会有谬误之处,希望得到指正



你可能感兴趣的:(【Technique】Python装饰器)