python函数装饰器一篇入魂

一、什么是装饰器

装饰器顾名思义就是装饰的工具,指的是在不改变被装饰对象的源代码以及调用方式的前提下,为被装饰对象增加额外的功能,装饰器经常使用在以下几个场景,比如:插入日志、性能测试、事务处理、缓存、权限校验、测试等应用场景

二、为何要有装饰器

软件的设计应该遵循开放封闭原则,即对扩展是开放的,而对修改是封闭的。我们对现有的代码需要扩展功能时不能修改源代码以及调用方式,否则稍有不慎,就会导致整个程序报错,因为我们要修改的这段程序可能在很多地方调用,所以修改源代码和调用方式是很不明智的。而装饰器则可以在不修改源代码和调用方式的情况为原程序拓展新的功能。

三、装饰器的实现思路

现在有一段程序,用来计算0~n的和

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

res = add(100000) 
print(res)

输出:
>> 4999950000

如果想计算一下这个函数的运行时间,在修改源代码的情况下我们是这样做的

import time


def add(n):
    start = time.time()
    sum = 0
    for i in range(n):
        sum += i
    stop = time.time()
    print('function run time: %s'%(stop - start))
    return sum


res = add(100000)
print(res)


输出:
>> function run time: 0.014251947402954102
>> 4999950000

上面的代码虽然实现了为函数体扩展计算函数运行时间的功能,但是违背了我们开头所说的开放封闭原则,为函数增加新功能是不能修改源代码的!那么在不修改源代码的情况下,如何为其加新功能呢?我们可以在调用的时候,计算函数体运行的时间

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

start = time.time()
res = add(100000)
print(res)
stop = time.time()
print('function run time: %s'%(stop - start))

输出:
>> 4999950000
>> function run time: 0.014489889144897461

现在实现了在不修改源代码以及调用方式的情况下,为原函数增加了计算运行时间的功能,既没有违背开放封闭原则,也扩展了新功能,看似是完美,但是有没有想过,我们可能在很多地方都调用了add函数,那我们要在每次调用的时候都写这么一堆代码?这完全是重复且冗余的代码,说到重复冗余的代码,有些同学就想到了,我们可以封装成一个函数啊!啧啧~

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def wrapper():
    start = time.time()
    res = add(100000)
    print(res)
    stop = time.time()
    print('function run time: %s'%(stop - start))

res = wrapper()
print(res)

输出:
>> 4999950000
>> function run time: 0.015117883682250977
>> None

好了,现在我们将拓展的功能封装成一个函数了,这样我们就省去了一大堆的重复冗余代码,但是大家有没有察觉到,上面的代码是有问题的

第一个问题: add 函数是有返回值的,但是我们的新函数wrapper(这里可以称之为装饰器了)返回值是None。

第二个问题 :调用 add 函数是需要传一个参数进去的,而装饰器函数却没有传参,这不就违背了文章开头提到的开放封闭原则了么

是的 所以我们还可以将函数优化一下
解决第一个问题:在装饰器函数里面要把调用add函数返回的值给return出来
解决第二个问题:在装饰器函数里面,把调用add函数时传的值参数化

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def wrapper(n):
    start = time.time()
    res = add(n)
    stop = time.time()
    print('function run time: %s'%(stop - start))
    return res

res = wrapper(10000)
print(res)

输出:
>> function run time: 0.0009837150573730469
>> 49995000

至此,我们已经在不违背开放封闭原则的前提下为 add 函数拓展了计算运行时间的功能,这样就大功告成了么? 不不不,我们现在只是实现了为 add 函数扩展了计算运行时间的功能而已,如果我们还需要为其他函数增加计算运行时间的功能怎么办,难道要再写一个吗?装饰器是通用的,所以我们要考虑一下,函数名能不能参数化?(函数对象的概念), 我们先尝试一下把函数名参数化,看看会有什么问题~

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def index(x,y):
    print('from index %s:%s'%(x,y))

def wrapper(func,n):
    start = time.time()
    res = func(n)
    stop = time.time()
    print('function run time: %s'%(stop - start))
    return res

res_add = wrapper(add,10000)
print(res_add)

wrapper(index,1,2)

输出:
>> function run time: 0.0007648468017578125
>> 49995000
  File "/Users/qts/PycharmProjects/QTS/test/ll.py", line 45, in <module>
    res_index = wrapper(index,1,2)
TypeError: wrapper() takes 2 positional arguments but 3 were given

将函数名参数化以后,新增index函数进行测试一下,调用wrapper为index函数增加扩展功能,出现了问题,什么问题呢?我们来看一下

第一个问题:index 函数是需要传两个参数的,但是装饰器函数wrapper 只有一个位置形参可以传值,另一个参数怎么传?

首先解决第一个问题,被装饰的对象有几个参数我们不知道,那么我们可不可以用不定长参数来接收呢,这里就用到args,**kwags来接收传进来的参数打包成一个元组或者字典,然后在函数体内使用args,**kwargs 把元组或者字典重新打散传值给func,这样就实现了我们给wrapper传什么参数,func接收的就是什么参数

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def index(x,y):
    print('from index %s:%s'%(x,y))

def wrapper(func,*args,**kwargs):
    start = time.time()
    res = func(*args,**kwargs)
    stop = time.time()
    print('function run time: %s'%(stop - start))
    return res

res_add = wrapper(add,10000)
print(res_add)

wrapper(index,1,2)

输出:
>> function run time: 0.0012209415435791016
>> 49995000
>> from index 1:2
>> function run time: 1.0013580322265625e-05

第二个问题:我们在调用的时候多传了一个形参func,因为我们需要通过func 传入被装饰的函数对象,装饰器函数改动了形参,改变了原函数的调用方式,违背了开头说的开放封闭原则

解决第二个问题就使用到了闭包函数的概念,不了解的小伙伴可以去百度一下,闭包函数其实就是第二种为函数体传参的方式,通过闭包函数的方式我们解决了第二个问题,现在我们就已经实现了完整的无参装饰器了。

import time

def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

def index(x,y):
    print('from index %s:%s'%(x,y))

def outter(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print('function run time: %s'%(stop - start))
        return res
    return wrapper


add = outter(add) 
res = add(10000)
print(add)

index = outter(index)
index(2,1)

输出:
>> function run time: 0.0017659664154052734
>> 49995000
>> from index 2:1
>> function run time: 1.621246337890625e-05

另外提一点命名空间的概念,调用outter函数返回的是wrapper的函数对象,我们在全局作用域拿到后可以赋值给x,可以赋值给y,也可以赋值给原函数名,把原函数名指向函数对象给覆盖,我们就像是在调用原函数名一样调用装饰器了,是不是很完美~,其实python就已经帮我们实现这件事了,这里要介绍一个令人快乐的东西叫语法糖

语法糖
在被装饰对象上面加上@装饰器,就相当于 add = outter(add) 然后我们调用add时,其实就是调用的outter函数返回的wrapper

import time

def outter(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print('function run time: %s'%(stop - start))
        return res
    return wrapper

@outter
def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

@outter
def index(x,y):
    print('from index %s:%s'%(x,y))

res = add(10000)
print(res)

index(1,2)

输出:
>> function run time: 0.001477956771850586
>> 49995000
>> from index 1:2
>> function run time: 3.314018249511719e-05

再多说一句哈,现在我们实现的装饰器是不是天衣无缝了?我们在调用的时候完全不用关心原函数扩展了啥功能,而且也不用改变原来的调用方式,但是还是有一点区别的,比如函数自带的一些方法 namedoc 还是有区别的,我们既然要伪装了,就一定要力争完美,这里可以使用 functools 的 wraps 方法

import time
from functools import wraps

def outter(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print('function run time: %s'%(stop - start))
        return res
    return wrapper

@outter
def add(n):
    sum = 0
    for i in range(n):
        sum += i
    return sum

@outter
def index(x,y):
    print('from index %s:%s'%(x,y))

无参装饰器讲完了,后续有机会讲一下有参装饰器以及叠加装饰器的用法~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包装饰器是一种特殊的装饰器,它使用闭包的概念来实现。闭包是指一个函数可以访问并操作其外部函数中定义的变量。在Python中,闭包装饰器可以用于给函数添加额外的功能,同时保持函数的原始定义不变。 引用中的示例展示了装饰器传参的形式。在这个例子中,outer函数是一个装饰器,它将inner函数作为子函数返回,并在inner函数中添加了额外的功能。通过使用@outer装饰器语法,我们可以在add函数上应用outer装饰器,从而在调用add函数时执行装饰器中的代码。 引用中的示例展示了多层装饰器的使用。在这个例子中,outer1和outer2函数分别是两个装饰器,他们都返回一个inner函数。通过使用@outer1和@outer2装饰器语法,我们可以在outers函数上应用这两个装饰器,并在调用outers函数时按照装饰器的定义顺序执行相关的代码。 引用提供了关于Python闭包装饰器的使用方法的总结。这篇文章通过示例代码详细介绍了闭包装饰器的使用,对于学习和工作有一定的参考价值。 引用中的示例展示了装饰器的形式。在这个例子中,outer函数是一个装饰器,它将inner函数作为子函数返回,并在inner函数中添加了额外的功能。通过使用@outer装饰器语法,我们可以在add函数上应用outer装饰器,从而在调用add函数时执行装饰器中的代码。 综上所述,Python闭包装饰器是一种利用闭包概念实现的特殊装饰器,可以用于给函数添加额外的功能。这种装饰器可以通过装饰器传参的形式、多层装饰器的形式或普通的装饰器形式来实现。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值