python装饰器的实例

装饰器是一个非常好用的功能,能使程序更加简洁,同时也减少代码量。那什么是装饰器呢?文字描述会比较苍白,还是举例子,大家更加容易接受。目前装饰器比较认同的应用有程序性能,插入日志等功能。

程序性能

有这么一个需求,要求输出每个函数的执行时长,以此来规避一些程序性能问题。

第一版
import time

def func_1():
    time.sleep(1)
    for i in range(10000):
        if i < 10000:
            continue
    return 1

start = time.time()
func_1()
end = time.time()
print("elapse_time:", end-start)

上述代码是最简单的实现函数func_1耗时的案例。
若要统计十个函数,那是不是要重复十遍 主程序呢?好吧,优化一下下:

第二版
import time

def func_1():
    time.sleep(1)
    for i in range(10000):
        if i < 10000:
            continue
    return 1

def elapse_time(func):
    start = time.time()
    func()
    end = time.time()
    print("elapse_time:", end-start)

elapse_time(func_1)

看起来简单多了,即使有多个函数需要统计性能,那么也只要多行调用代码即可:

第三版
import time

def func_1():
    time.sleep(1)
    for i in range(20000):
        if i < 20000:
            continue
    return 1

def func_2():
    time.sleep(1)
    for i in range(30000):
        if i < 30000:
            continue
    return 1

def func_3():
    time.sleep(1)
    for i in range(10000):
        if i < 10000:
            continue
    return 1

def elapse_time(func):
    start = time.time()
    func()
    end = time.time()
    print("elapse_time:", end-start)

elapse_time(func_1)
elapse_time(func_2)
elapse_time(func_3)

那么问题来了,如果有更多的函数,如果要统计更多的函数的程序性能的话,上述方法看起来就不太够用了。有什么更好的方法呢?就是接下去要说的装饰器了(在需处理的函数定义前使用语法糖@+函数名)。

第四版
import time

def elapse_time(func):
    start = time.time()
    func()
    end = time.time()
    print("elapse_time:", end-start)

@elapse_time
def func_1():
    time.sleep(1)
    for i in range(20000):
        if i < 20000:
            continue
    return 1

@elapse_time
def func_2():
    time.sleep(1)
    for i in range(30000):
        if i < 30000:
            continue
    return 1

@elapse_time
def func_3():
    time.sleep(1)
    for i in range(10000):
        if i < 10000:
            continue
    return 1

func_1
func_2
func_3

第四版调用函数的时没有括号总觉得怪怪的,可读性不是很高,所以需要调整下:

第五版
import time

def elapse_time(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print("elapse_time:", end-start)
    return wrapper

@elapse_time
def func_1():
    time.sleep(1)
    for i in range(20000):
        if i < 20000:
            continue
    return 1

@elapse_time
def func_2():
    time.sleep(1)
    for i in range(30000):
        if i < 30000:
            continue
    return 1

@elapse_time
def func_3():
    time.sleep(1)
    for i in range(10000):
        if i < 10000:
            continue
    return 1

func_1()
func_2()
func_3()

第五版与第三版的代码量其实没有多大差别。当被修饰的函数(func_1,func_2,func_3)多次调用时,第三版每次调用前都要用上 elapse_time()函数,而第五版不需要去关心这个,不管调用多少次都会统计每次的程序执行时间。此外,也不需要在调用被修饰函数时,做任何处理,减轻了代码的维护成本。第五版看似已经满足要求了,但是通常情况下,我们执行的fun_1,fun_2,fun_3都会带参数,那么如何处理呢?

第六版
import time

def elapse_time(func):
    def wrapper(*args):
        start = time.time()
        func(*args)
        end = time.time()
        print("elapse_time:", end-start)
    return wrapper

@elapse_time
def func_1(time_sleep,range_num):
    time.sleep(time_sleep)
    for i in range(range_num):
        if i < range_num:
            continue
    return 1

func_1(1,200000)

定义一个可变参数*args,如果有必要还可以定义**kwargs

插入日志

上述介绍了一个简单的案例,接下来看看插入日志功能如何实现:

第一版
import logging

def insert_log(func):
    def wrapper(*args):
        logging.info("Function {0} is running".format(func.__name__))
        return func(*args)
    return wrapper
@insert_log
def func():
    print("I am func")


logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()

So Easy ,不难嘛。加大一点难度,插入日志的装饰器需要带参数(插入日志前,需判断日志是否需要打印):

第二版
import logging

def insert_log(is_print=False):
    def deractor(func):
        def wrapper(*args):
            if is_print:
                print("Function {0} is running".format(func.__name__))
                logging.info("Function {0} is running".format(func.__name__))
            return func(*args)
        return wrapper
    return deractor

@insert_log(is_print=True)
def func():
    print("I am func")

logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()

实现起来也没有那么复杂,其实就是在最外层再包一层即可。在修饰时,可带上参数。
如果既要打印日志,又要统计函数运行时间应该如何处理呢?

第三版
import time
import logging

def elapse_time(func):
    def wrapper(*args):
        start = time.time()
        func(*args)
        end = time.time()
        print("elapse_time:", end-start)
    return wrapper

def insert_log(is_print=False):
    def deractor(func):
        def wrapper(*args):
            if is_print:
                print("Function {0} is running".format(func.__name__))
                logging.info("Function {0} is running".format(func.__name__))
            return func(*args)
        return wrapper
    return deractor

@elapse_time
def func_1(time_sleep,range_num):
    time.sleep(time_sleep)
    for i in range(range_num):
        if i < range_num:
            continue
    return 1

@elapse_time
@insert_log(is_print=True)
def func():
    print("I am func")

logging.basicConfig(filename="demo1.log",filemode="a",level="DEBUG")
func()

其实使用多个装饰器即可。需要注意的是,下层的装饰器执行悠闲于上层的装饰器。上述代码:

@elapse_time
@insert_log(is_print=True)
def func():
    print("I am func")

程序运行时,@insert_log(is_print=True)装饰器先执行,后执行@elapse_time装饰器。
运行结果如下:

D:\software\Python3.4.3\Install\python3.exe D:/scripts/git/others/demo1.py
Function func is running
I am func
elapse_time: 0.003000020980834961

Process finished with exit code 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值