Python 装饰器的用法,不改变函数源码,增加新的功能



Python装饰器的理解

Python装饰器(Decorators)用于不改变函数源代码的基础上,对函数添加新的功能。可比如:人的衣服,不改动衣服原材料的情况下,可以在上面加个小红花之类的。

装饰器可以作用在,平常购买商品时,点击购买会将购买商品的函数放入进去,如果是登录状态则可以执行这个函数,未登录的话,则跳转到登录界面,不执行购买函数。

在理解Python装饰器前得先理解一下闭包的概念:

博主写了一篇关于闭包的博客,可以参考一下:https://blog.csdn.net/m0_46958731/article/details/110336617


Python装饰器的使用

一个普通计算函数运行时间的函数

import time	# 记得导入这个时间模块

def run_time(func):	# 接收一个函数对象
    def wrapper(*args,**kyargs):	# 为了防止被装饰函数需要传参
        start = time.time()
        
        func(*args,**kyargs)	# 调用了传递进来的函数对象,且将值传入(不管是否有值,写上即可)
        
        stop = time.time()
        print('run %s time is %0.2fs' % (func.__name__,stop - start))

    return wrapper # 调用run_time函数后,把wrapper装饰器函数返回给调用者

调用创建好的装饰器

# 定义一个函数,待会被装饰的就是它了
def index():
	print('my name is index')
	time.sleep(1)

test_index = run_time(index)	# 将index函数对象传递进去
# test_index = run_time(index) = wrapper
# 接收到run_time函数的返回值 wrapper的函数对象:test_index = wrapper

test_index()	# 调用 wrapper这个装饰器函数

执行效果:

'my name is index'
'run index time is 1.00s'

以上代码给index函数简单实现了一个计算运行时间的装饰函数,不过调用的方式却发生了改变,并不是理想中装饰器的样子,我们需要稍加改变!

是不是感觉少了点啥,少了@装饰器语法糖的使用,但其实装饰器就是就一个方法,添加@只是少了一行传递被装饰函数的代码

未使用语法糖前
test_index = run_time(index)
# 只是可以自定义变量名,也可以将变量名定义成index
test_index()

使用语法糖以后:把调用代码化简,放在被装饰函数的上方即可
@run_time	# index = run_time(index)
def index():
	print('my name is index')
	
index()	# 可以调用装饰好以后index函数了,其实还是相当于在调那个wrapper

调用结果:

'my name is index'
'run index time is 1.00s'

@语法只是将被装饰函数传递到装饰器函数,与未使用@时调用结果一样

其实我们调用的index已经被wraning所替换,我们可以看一下

@run_time  # run_time(index)
def index():
    print('my name is index')

print(index)

打印结果

<function run_time.<locals>.wrapper at 0x7f987af0ec10>

wraps装饰器的使用

如果我们想要做到真正的隐藏起来,让调用者看起来就是在调用index函数,那么我们可以使用一个Python提供的装饰器wraps

import time
from functools import wraps
def run_time(func):  # 接收一个函数对象
    @wraps(func) # 将传递的被装饰函数,也就是index,进行各项属性隐藏
    def wrapper(*args, **kyargs):  # 为了防止被装饰函数需要传参
        start = time.time()

        func(*args, **kyargs)  # 调用了传递进来的函数对象,且将值传入(不管是否有值,写上即可)

        stop = time.time()
        print('run %s time is %0.2fs' % (func.__name__, stop - start))

    return wrapper  # 调用run_time函数后,把wrapper装饰器函数返回给调用者

@run_time  # run_time(index)
def index():
    print('my name is index')

print(index)

执行结果

<function index at 0x7fe9fff0dc10>

关于这个Python提供的装饰器,我们不必深入了解,知道其使用方法及效果即可


装饰器处理被装饰函数的返回值

相同的代码,处理计算的功能

装饰器写法,几乎没有区别,加入return只是被装饰函数会有返回值

def calculation(func):  # 接收一个函数对象
    def wrapper(*args, **kyargs):  # 为了防止被装饰函数需要传参

        print('装饰器将 %s 传递给了 %s 函数' % (args,func.__name__))
        res = func(*args, **kyargs)  # 将传递给wrapper函数的值交函数对象,这里就是交给下面index函数了
        # res拿到index函数的返回值

        return res  # 将得到的值返回给调用者

    return wrapper  # 调用run_time函数后,把wrapper装饰器函数返回给调用者

被装饰函数

@calculation
def index(x,y):
    print('index接收到了 %s %s 参数' % (x,y))
    return x + y   # 当调用到index时,返回x+y的处理结果



# 注意:这个index并不是代表上面index函数,因为经历了:index = calculation(index) 将index函数作为对象传递进去
# 然后这个index变量 拿到wrapper的返回对象了,这时index = wraper
# 理解为:index = calculation(index) = wrapper

print('拿到装饰器的返回值:',index(1,10))	# 调用wrapper装饰器函数,并向它传递两个值,调用之后拿到 wrapper函数的返回值并且打印

执行效果:

'装饰器将 (1, 10) 传递给了 index 函数'
'index接收到了 1 10 参数'
'拿到装饰器的返回值了: 11'

带参数的装饰器

装饰器可以携带参数传入,一个携带参数的装饰器有三层

def outter(parameter): # 接收被调用时传递的参数
    print('装饰器接收到的一个参数:',parameter)
    def decorators(func):
        def wrapper(*args,**kwargs):
            func(*args,**kwargs)

        return wrapper

    return decorators # 将装饰器函数对象,返回给调用者

# 在@语法调用装饰器时,给它传递了一个参数
@outter("this is decorators parameter")
def index():
    print('my name is index')

index()

打印结果:
'装饰器接收到的一个参数: this is decorators parameter'
'my name is index'

或许这样看会有些迷惑,但我们恢复成不用@语法就能通俗易懂了

outter = outter('this is decorators parameter')
# 调用装饰器外层函数,传递一个参数进去,然后它返回了装饰器decorators的函数对象

index = outter(index) # 哈哈,又变成了和上序一样的写法, index = outter(index) = wrapper
index() # 调用wrapper装饰函数

多装饰器叠加的用法

不必一次掌握,理解完上面再来看这种用法不会那么复杂

多装饰器指的是每个装饰器的函数对象都是其下面那个装饰器的,多装饰器的加载是从下至上开始的,而执行是从上执行,下面代码说明:

# 先从下面调用装饰器的部分看
def timmer(func): # func = wrapper2
    print('this is timmer decuretion')
    def wrapper1(*args,**kwargs):
       print('这里是timmer函数的wrapper1')
       func() # 调用wrapper2函数
    return wrapper1

def login(func): # func = index
    print('this is login decuretion')
    def wrapper2(*args,**kwargs):
        print('这里是login函数的wrapper2')
        func() # 调用index函数
    return wrapper2 # 加上序号更加清晰说明
    

# 先演示装饰器的效果,这里只是加载

# 这里是重点!!!!多装饰器加载从下至上看
@timmer # 2:注意:这里并不是向timmer传递index,而是传递login拿到的函数对象wrapper2
@login  # 1:向login函数传递index的函数对象,拿到wrapper2函数对象
def index():
    print('this is index')

打印结果为:
'this is login decuretion'
'this is timmer decuretion'
# 为什么是这样呢,因为叠加装饰器是从下至上加载,也就是语法糖指向的函数
# 它们拿到了对应的函数对象,但是并未调用

print(index)查看index属于哪一个函数
# 注意看过装饰器用法后后就会知道,经过装饰以后,我们看到的index函数,调用时却不是调用它。

<function timmer.<locals>.wrapper1 at 0x7fdc5160ee50>
# 由于从下至上加载后,index指向的就是最上面那个装饰器的wrapper1函数对象

调用效果:

print('=' * 30) # 划开界线

index() # 调用index

# 可以看到,装饰器加载完毕外部函数是从下至上,调用执行装饰函数是从上至下
this is login decuretion
this is timmer decuretion
==============================

# 调用后的结果,从上至下执行wrapper函数
这里是timmer函数的wrapper1
这里是login函数的wrapper2
this is index

实例:验证用户信息同时,计时函数使用时间的效果

import time
def timmer(func): # func = index函数对象 (别懵逼,注意装饰器的执行顺序)
    print('this is timmer decuretion')
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        stop = time.time()
        print('run %s time is %0.2fs' % (func.__name__,stop-start))
        return res
    return wrapper


def login(func): # func = timmer.wrapper timmer下面的函数对象
    print('this is login decuretion')
    def wrapper(*args,**kwargs):
        user = input('Please your name >>>>>> ')
        password = input('Please your password >>>>>> ')
        if user == 'root' and password == 'root':
            print('login successful')
            res = func(*args,**kwargs)
            return res
        else:
            print('username or password error')
    return wrapper

# 执行顺序的不同,函数的执行效果也将不同

@login # 拿到timmer返回的wrapper,传递给login
@timmer # 向timmer传递index函数对象,返回了wrpaper
def index():
    time.sleep(0.5)
    print('from index')

# 调用函数后,首先执行login.wrapper函数,登录成功后,再执行timmer.wrapper函数
index()
# 统计时间也就是index函数所用的时间,0.5s


# 而如果这样写的话:
@timmer # 调用login.wrapper
@login  # 调用index

# 先执行timmer调用login.wrapper函数,那我们统计的就是login.wrapper函数所用的时间了

总结:多装饰器叠加,我们从最上面装饰器开始看,它所调用的就是第二个装饰器的wrapper函数,而第二个则调用的是第三个里面的wrapper函数,直至最后一个调用的才是index函数


到此为止,装饰器也并没有想象中的那么复杂。

装饰器语法体现了函数作为Python中的一等公民,可以是对象,变量,也可以作为参数和返回值,说明了Python函数的强大与灵活。

装饰器模板

这里提供一个常用的装饰器模板,在以后使用装饰器时,可以直接将以下模板放入。

from functools import wraps
def decorate(func):
    @wraps(func)
    def wrapper(*args, **kyargs):
        return func(*args, **kyargs)
    return wrapper
@decorate
def index():
    pass

return是避免调用被装饰函数需要返回值


技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点赞 收藏+关注 谢谢支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值