【自动化运维番外篇】Python装饰器

【摘要】

大多数编程语言都提供了一些奇技淫巧,通常叫做语法糖,对于语法糖的定义如下:

1.计算机语言中特殊的某种语法

2.这种语法对语言的功能并没有影响

3.对于编程人员有更好的易用性

4.能够增加程序的可读性

Python中同样也有很多语法糖,比如:列表推导式,with上下文管理器,装饰器等。其中装饰器是一个十分重要的特性,并且在之前面向对象章节中类的讲解,以及Flask框架的讲解中,也都或多或少使用过装饰器。

今天这一章节,就来着重给大家讲解一个Python中的装饰器

【定义】

装饰器是从Decorator直译而来,它可以在不改变一个函数代码和调用方式的情况下给函数添加新的功能

装饰器本质上是一个闭包函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。这样我们可以在不改变被装饰函数的代码的情况下给被装饰函数或程序添加新的功能。

Python的装饰器广泛应用于缓存、权限校验(如Flask中的@login_required和@permission_required装饰器)、性能测试(比如统计一段程序的运行时间)和插入日志等应用场景。

有了装饰器,我们就可以抽离出大量与函数功能本身无关的代码,增加一个函数的重用性。

先看一个最简单的装饰器例子,代码如下:

import time

def timer(func):
    def inner(num):
        start = time.time()
        func(num)
        end = time.time()
        print("%s cost %f seconds" % (func.__name__, end - start))
    return inner

@timer
def foo(num):
    time.sleep(num)


if __name__ == "__main__":
    foo(3)
    
# 运行程序后输出
# foo cost 3.000634 seconds

上述代码中的timer函数就是一个装饰器函数,它接收另外一个函数作为参数,并统计其运行的时间。

foo函数是定义的一个示例函数,在其定义语句上面增加一行@timer就是给它增加了一个装饰器。

所以@其实就是Python中装饰器的语法糖。

【闭包】

装饰器函数其实本质上就是一个闭包函数,上面代码示例中的timer就是一个闭包函数。

闭包是Python编程一个非常重要的概念。如果一个函数中定义了一个内函数,且内函数体内引用到了体外的变量,这时外函数通过return返回内函数的引用时,会把定义时涉及到的外部引用变量和内函数打包成一个整体(闭包)返回。

闭包的定义可能会有点儿晦涩难懂,我们通过下面的代码示例详细解释一下:

def outer(a):
    def inner(b):
        print(a + b)
    inner(2)   
    
outer(2)
# 输出结果为 3

上面代码中的inner就是内函数,内函数中定义了一个局部变量b,除此之外还引用到了其函数外部的a变量。

a变量本身不属于inner函数的变量作用域,但如果inner函数使用a变量时无法在自己的局部作用域中找到该变量时,会继续向上一层作用域中寻找,而inner函数的上一层作用域就是outer函数,a变量就是outer函数的参数,所以此时inner函数就找到了a变量。

当调用outer()函数时,其实outer内部又调用了inner函数,但如果在outer函数里不调用inner函数,而是返回inner函数的引用,会有什么效果呢?代码如下:

def outer(a):
    def inner(b):
        print(a + b)
    return inner 
    
f = outer(2)
print(type(f))
print(f)
print(f.__name__)

# 输出结果为
<class 'function'>
<function outer.<locals>.inner at 0x7f9fd8093280>
inner

从代码可以看出,执行f = outer(2)之后,并没有任何输出结果,内部也没有运行inner函数,而是将inner函数的引用返回了出来,所以f就是inner函数的引用。

如果想要调用inner函数,就需要如下写法:

def outer(a):
    def inner(b):
        print(a + b)
    return inner 
  
f = outer(2)
f(1)
# 输出结果为 3
# 或者写成 outer(2)(1) 

光从变量的作用域中仍无法体现闭包的精髓。

正常情况下,一个函数运行结束的时候,临时变量会被销毁,所以上面代码中执行了f = outer(2),此时其实outer函数已经执行结束了,所以outer函数中的局部变量a理应被销毁。

但由于我们在outer函数中返回了inner函数的引用,而inner函数又使用了outer函数中的局部变量,所以执行完f = outer(2)后,还不能将被inner函数引用到的变量a销毁,而是会将outer函数的局部变量同inner函数绑定在一起,并将inner函数返回,以供后续调用。

所以outer函数中最后的return inner做了两件事情,一件是返回了inner函数的引用,另一件就是暂时保留了被inner函数引用到的外部变量。

综上所述,闭包就是即使外函数已经结束了,内函数仍然能够使用外函数的临时变量

【装饰器】

在了解了闭包之后,就可以开始真正了解Python中的装饰器了。

计算一个函数执行时间的闭包代码如下所示:

import time

def timer(func):
    def inner(num):
        start = time.time()
        func(num)
        end = time.time()
        print("%s cost %f seconds" % (func.__name__, end - start))
    return inner

timer函数是一个外函数,并接受一个参数funcinner函数是一个内函数,在inner函数中使用了timerfunc变量;最终返回了inner函数的引用。

比较特殊的是timer函数可以接受的是一个函数的引用,所以我们可以做如下事情:

def foo(num):
    time.sleep(num)
    
timer(foo)(3)

# 输出结果为 foo cost 3.000634 seconds

timer(foo)(3)这种写法总让人感觉很奇怪,易读性很低,而且编程人员写起来也不优雅。这时候就到了语法糖发挥作用的时候了。

Python中@就是一个语法糖,它的作用就是将被装饰函数的引用当作参数传入装饰函数中,代码如下:

import time

def timer(func):
    def inner(num):
        start = time.time()
        func(num)
        end = time.time()
        print("%s cost %f seconds" % (func.__name__, end - start))
    return inner
  
@timer
def foo(num):
    time.sleep(num)
    
foo(3)

# 输出结果为 foo cost 3.000634 seconds

所以foo函数被装饰后,在调用foo函数时,会先将foo函数的引用传入了timer函数中,再执行timer函数的返回值,等价于timer(foo)(3)

【总结】

装饰器始终是Python中十分重要的一个语法糖,可以说随处可见,这一章节主要从实现的角度给大家讲解了一下装饰器,也是希望大家能够在Python给我提供的便利下,窥见更多实现原理和细节,并且这种深入的理解对以后的编程也会起到很大的作用。

下一章节我会讲解规范的Python代码中是如何使用装饰器的,并且带领大家对更深一步的源码进行阅读,敬请期待。


欢迎大家添加我的个人公众号【Python玩转自动化运维】加入读者交流群,获取更多干货内容
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值