博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
19.python装饰器
阅读量:5031 次
发布时间:2019-06-12

本文共 5759 字,大约阅读时间需要 19 分钟。

情景模拟

通过对一个功能需求的分析和解决过程来探究一下“装饰器是什么”以及“装饰器的一些特性”。

1.场景说明

假设我现在已经定义了一些函数,并且这些函数都已经被线上业务广泛应用。

import timedef func1():    time.sleep(1)    print('func1')    return 'func1'def func2():    time.sleep(2)    print('func2')    return 'func2'func1()func2()

2.功能需求

现在线上某个业务响应时间过长,需要在不影响线上服务的情况下分别统计这些函数的运行时间来定位故障。

需求解释:既然不能影响线上业务,那么必然是不能要求函数调用方去更改代码的,这当然包括调用方式。

实现方式:

  • 为每个函数单独添加重复的代码
  • 把统计运行时间的的代码封装成一个可接收函数作为参数的高阶函数
  • 使用嵌套函数改进上面的高阶函数

3.实现方式及改进的过程

初步实现:分别为各个函数添加运行时间统计功能

import timeimport sysdef func1():    start_time = time.time()    time.sleep(1)    print('func1')    end_time = time.time()    func_name = sys._getframe().f_code.co_name    print('%s run time is %s'%(func_name,(end_time - start_time)))    return 'func1'def func2():    start_time = time.time()    time.sleep(2)    print('func2')    end_time = time.time()    func_name = sys._getframe().f_code.co_name    print('%s run time is %s'%(func_name,(end_time - start_time)))    return 'func2'func1()func2()

输出结果:

func1func1 run time is 1.0000572204589844func2func2 run time is 2.0001142024993896

存在问题:

  • 如果涉及的函数太多,那么需要做大量的重复性工作;
  • 如果这些函数分散在不同的模块,且有不同的人维护,那么需要进行协商沟通来保证代码功能一致性;
  • 故障解决后,还需要一个一个的去删除或注释调试代码,又是一次大量的重复性工作;
  • 如果其他业务也遇到相同问题,需要再次在多个地方复写这些代码。

改进思路:

要避免重复劳动,提高代码重用性,大家很自然就会想到把这个功能封装成一个函数。

改进:自定义一个(高阶)函数来提高统计代码的复用性

import timedef func1():    time.sleep(1)    print('func1')    return 'func1'def func2():    time.sleep(2)    print('func2')    return 'func2'def print_run_time(f):    time_start = time.time()    ret = f()    time_end = time.time()    func_name = f.__name__    print('%s run time is %s'%(func_name,(time_end - time_start)))    return retprint_run_time(func1)print_run_time(func2)

输出结果:

func1func1 run time is 1.0000572204589844func2func2 run time is 2.0001144409179688

存在问题:

统计代码的重复性工作解决了,但是新的问题出现了:此时只能通过print_run_time(f)函数去调用原来的函数了(func1, func2, ...),这显然已经改变了函数的调用方式,因此是不合理的。

改进思路:

如果函数调用方式不能修改,那么我们只能给原来的函数名重新赋值一个新的函数体了,这个新的函数体就应该是添加完统计功能之后的函数体。我们看看下面这两种实现行不行:

func1 = print_run_timefunc2 = print_run_time

print_run_time(f)是有参数的,而原函数func1和func2是没参数的,调用方式还是发生改变了,因此这种方式不可行。

func1 = print_run_time(func1)func2 = print_run_time(func2)

上面这种方式显然更不行了,因为print_run_time(f)返回的是原函数的返回值,而这个返回值不是一个函数,这将导致被重新赋值后的func1和func2无法被调用。

那么我们是否可以把print_run_time(f)函数的函数体定义为一个内部的嵌套函数,然后将这个内部的嵌套函数作为print_run_time(f)函数的返回值呢?这貌似是说的通的,看下面的实现。

改进2:使用嵌套函数和闭包改进上面定义的高阶函数

import timedef func1():    time.sleep(1)    print('func1')    return 'func1'def func2():    time.sleep(2)    print('func2')    return 'func2'def print_run_time(f):    def inner():        time_start = time.time()        ret = f()        time_end = time.time()        func_name = f.__name__        print('%s run time is: %s' % (func_name, (time_end - time_start)))        return ret    return innerfunc1 = print_run_time(func1)func2 = print_run_time(func2)func1()func2()

输出结果:

func1func1 run time is: 1.0000572204589844func2func2 run time is: 2.0001142024993896

Cool! We got it! 我们现在所需要的做的是在所有需要统计运行时长的函数定义之后的任意地方执行一下下面这条语句就可以了:

funcN = print_run_time(funcN)

存在问题:忽略了原有函数参数

改进思路:是的,我们定义print_run_time(f)函数的内部函数inner()时,为它定义相应的参数就可以了。

由于每个函数的参数数量是不同的,因此inner函数的参数应该是可变(长)参数。

改进3:支持原函数传递参数

import timedef func1(string):    print(string)    time.sleep(1)    print('func1')    return 'func1 return'def func2():    time.sleep(2)    print('func2')def print_run_time(f):    def inner(*args, **kwargs):        time_start = time.time()        ret = f(*args, **kwargs)        time_end = time.time()        func_name = f.__name__        print('%s run time is: %s' % (func_name, (time_end - time_start)))        return ret    return innerfunc1 = print_run_time(func1)func2 = print_run_time(func2)ret1 = func1('decorator test!')print(ret1)ret2 = func2()print(ret2)

输出结果:

decorator test!func1func1 run time is: 1.0000572204589844func1 returnfunc2func2 run time is: 2.0001144409179688None

到目前为止:统计函数运行时间的功能实现了,函数原来的调用方式没有发生改变,原函数的定义也没有发生改变。其实这就是是装饰器的雏形。

装饰器的概念和原理


1. 什么是装饰器?

装饰器,是一种“语法糖”,其本质上就是个函数。

2. 装饰器的作用

它是一个装饰其他函数的函数,用来为其他函数添加一些额外的功能。

3. 装饰器原则

装饰器对被装饰的函数应该是完全透明的,即

  • 不能修改被装饰的函数的源代码
  • 不能修改被装饰的函数的调用方式

4. 什么样的函数才是装饰器?

高阶函数 + 嵌套函数 => 装饰器

这里的高阶函数需要同时满足以下两个条件:

  • 接收函数名作为参数 -- 可以实现在不修改被装饰函数源代码的情况下为其添加新的功能
  • 返回内部嵌套函数的函数名 -- 可以实现不用修改函数的调用方式

5. 装饰器实现实例

再来看下上面写的实现代码:

def func1(string):    print(string)    time.sleep(1)    print('func1')    return 'func1 return'def func2():    time.sleep(2)    print('func2')    def print_run_time(f):    def inner(*args, **kwargs):        time_start = time.time()        ret = f(*args, **kwargs)        time_end = time.time()        func_name = f.__name__        print('%s run time is: %s' % (func_name, (time_end - time_start)))        return ret    return inner

对于这段代码来讲,print_run_time(f)就已经是一个装饰器函数。为了不改变原函数的调用方式,我们需要把print_run_time(f)函数的返回值重新赋值给原来的函数名:

func1 = print_run_time(func1)func2 = print_run_time(func2)

但是我们上面说过,装饰器除了是一个函数之外,还是一个“语法糖”。“语法糖”应该是可以简化某些操作的,事实上确实是这样。上面的过程其实可以这样来写:

import timedef print_run_time(f):    def warpper(*args, **kwargs):        time_start = time.time()        ret = f(*args, **kwargs)        time_end = time.time()        func_name = f.__name__        print('%s run time is: %s' % (func_name, (time_end - time_start)))        return ret    return warpper@print_run_timedef func1(string):    print(string)    time.sleep(1)    print('func1')    return 'func1 return'@print_run_timedef func2():    time.sleep(2)    print('func2') func1('Decorator Test...') func2()

 

@print_run_timedef func1(string):    ...

相当于:

func1 = print_run_time(func1)

不要问为什么,因为Python解释器就是这样执行的。但是需要注意,此时print_run_time(f)函数必须定义在被修改的函数定义之前,这个很容易理解,只要捋一下代码执行过程就明白了。另外,我们把print_run_time的内部函数名改成了wrapper,这个是装饰器函数的惯用名称(当然,也可以继续使用inner或使用任意名称)。

那么,现在我们可以像原来那样调用函数了(上面的修改这对函数的调用方是完全无感知的):

输出结果:

Decorator Test...func1func1 run time is: 1.0006635189056396func2func2 run time is: 2.0003299713134766

 

 

 

转载于:https://www.cnblogs.com/pengp/p/6876109.html

你可能感兴趣的文章
代码生成工具更新--快速生成Winform框架的界面项目
查看>>
C++编程思想重点笔记(上)
查看>>
网络连接
查看>>
rest 参数和扩展运算符
查看>>
Struts中Action结果集路径解释
查看>>
SSM-WebMVC(三)
查看>>
java.lang.NoClassDefFoundError: org/apache/http/ssl/SSLContexts
查看>>
简单上传图片代码
查看>>
html 表单动态添加输入项,并以数组的形式发送
查看>>
consul-服务发现、服务隔离、服务配置
查看>>
u-boot中网口处理--硬件部分
查看>>
pickle序列化与反序列化 + eval说明
查看>>
ACM学习历程—UESTC 1222 Sudoku(矩阵)(2015CCPC H)
查看>>
CentOS6.5以runlevel 3开机时自动连接某无线设置示例
查看>>
网络流
查看>>
WPF文本控件及实现(1)
查看>>
16款实用的jQuery商城分类导航菜单代码
查看>>
多重引号的嵌套使用
查看>>
Win7+vs2017+opencv
查看>>
ffmpeg安装
查看>>