python闭包和装饰器

文章介绍了Python中的闭包概念,以及如何使用闭包实现装饰器。装饰器允许在不修改原函数代码的情况下,为函数添加额外功能,如计算执行时间。文中通过逐步改进的方法展示了装饰器的实现,从直接修改函数,到使用高阶函数,再到利用闭包实现调用方式不变的功能扩展。最后,文章提到了Python的语法糖`@decorator`,简化了装饰器的使用,并讨论了如何处理带参数的装饰器问题。
摘要由CSDN通过智能技术生成

一、 闭包

要理解装饰器需要先理解闭包,因为装饰器写法用到了闭包

定义:在一个函数里定义一个函数,内函数是用来外函数的一个变量,外函数的返回值是内函数的函数名(函数对象)

 def outer():
    a = 10
    def inner():
        print(a)
    return inner
    
# 要调用inner()函数打印,可以outer()()=inner()
# outer()() 可以拆解为两步,首先执行outer(),返回inner函数名,然后再执行一个括号,相当于inner(),可以用一个变量接收下返回值再给这个变量加括号,即outer()=inner,outer()()=inner()

inner可以引用变量a,因为自己这层找不到,会向外寻找

二、 简单装饰器的实现

定义:装饰器本质上是一个函数,该函数用来处理其他函数,在已有函数功能上,可以不修改已有函数代码实现扩展功能,为它们增加额外的功能,装饰器的返回值也是一个函数对象,简言之,装饰器就是为已经存在的对象添加额外的功能

需求:为所有已有函数增加一个执行时间计算的功能

方案一:直接修改测试用例代码,执行前时间是开始时间,执行后时间是结束时间,相减就是耗时

import time 

def foo():
    start_time = time.time()
    time.sleep(1)
    end_time = time.time()
    print("运行时间:",end_timestart_time)
    
foo()

缺点:1、直接修改原代码,修改了已经测试通过没问题的代码,引入了新代码,就可能引入bug,最好是对在不改变原有代码逻辑的基础上扩展代码;2、这个方案所有函数都需要改代码,成本高

方案二:借助高阶函数,定义一个show_time函数,参数传入foo函数名,调用show_time函数

import time

def foo():
    time.sleep(1)
     
def show_time(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print("运行时间:",end_time-start_time)

show_time(foo) 

优点:上面的方式没有修改foo函数内部逻辑,也没有重写foo函数

缺点:调用的函数名变了,从foo变成了show_time,如果foo函数是上游,下游有依赖会调用foo,那么如果下游如果不跟着改变,下游就会报错,如果之前就有同事会调用foo函数,那么他那就会报错,因为有依赖,他还得改代码,需要将对foo的调用全部改为show_time,成本高

方案三:在方案二的基础上,实现调用show_time()就相当于调用foo(),那么问题就解决了。要实现这个,需要用到闭包,show_time会返回一个函数对象,这个函数对象内是核心业务函数(里面对已有函数功能实现了扩展),由foo来接收,相当于foo指向了核心业务函数,调用foo就是调用核心业务函数,实现了偷梁换柱,没有改变函数调用方式

import time

def foo():
    time.sleep(1)
     
def show_time(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print("运行时间:",end_time-start_time)
    return inner

foo = show_time(foo)  # foo = inner
foo() 

"""
上述代码执行过程:
1、foo = show_time(foo),调用show_time函数,进入函数内部,第一行是函数定义,不会执行,直接调到return inner,然后foo = inner,实现了偷梁换柱
2、再执行foo(),相当于执行inner(),找到inner函数定义的地方进行执行,计算了时间并返回
"""

foo = show_time(foo),相当于foo = inner,这个时候foo已经指向inner,而inner函数就是对foo函数功能的扩充,是核心业务函数,再调用foo(),就是调用inner(),实现了偷梁换柱

思考:如果show_time里面不写inner函数,直接自己实现逻辑,也就是说show_time内部自己就是和新代码,然后返回show_time行不行?

答案:不行,确实foo = show_time(foo)这句没问题,会得到foo=show_time,然后foo()就相当于执行show_time(),但是会发现,此时调用show_time没有传入参数,执行时会报show_time缺少一个参数,而如果此时foo = inner这种实现方式就没问题,相当于调用inner(),而在inner函数的定义里,没有参数,所以这时候不会报错

三、 语法糖

方案三小缺点:其他函数要使用show_time功能,每次都要进行foo = show_time(foo)这样的赋值,挺麻烦的

解决方案:python提供了一个语法糖功能,在被装饰函数上面@装饰函数,是从上到下的执行逻辑,所以装饰函数需要放在上面

import time

def show_time(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print("运行时间:",end_time-start_time)
    return inner

@show_time  
def foo():
    time.sleep(1)
     
foo() 

# @show_time这一句就相当于foo = show_time(foo),效果是相同的,也就是调用foo相当于调用inner函数

至此,我们实现了在没有修改已有函数功能的基础上实现了功能扩展,没有修改函数调用方式,且可以利用语法糖方便的进行扩展功能调用

四、结合项目使用进一步完善装饰器功能

先写show_time装饰器,并测试通过

在被装饰函数模块导入show_time,给被装饰函数登录接口增加装饰器@show_time

报错了,inner函数定义时没有参数,但是login传了两个参数(一个是LOGIN_DATA,一个是默认参数getToken=False),所以需要修改装饰器,让inner函数接收参数,传递给func()

然后再次运行被装饰函数

再次修改装饰器

再次执行被装饰函数,成功

跑登录测试脚本代码,可以看到三条登录用例(数据驱动)都计算了接口耗时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值