day17:一文弄懂“无参装饰器”、“有参装饰器”和“叠加装饰器”

一、无参装饰器

1. 什么是装饰器

装饰:指的是为其他事物添加额外的东西点缀
器:指的是工具,可以定义成成函数

合到一起的解释:
装饰器指的定义一个函数,该函数是用来为其他函数添加额外的功能

2. 为何要用装饰器

开放封闭原则
开放:指的是对拓展功能是开放的(新增功能)
封闭:指的是对修改源代码是封闭的

<装饰器>:就是在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能

3. 如何用

需求:在不修改index函数的源代码以及调用方式的前提下为其添加统计运行时间的功能

'''案例:思考如何为该函数添加统计时间的功能'''

def index(x,y):
    print('index函数打印:%s %s'%(x, y))

index(11, 22)
index(11, y=22)
index(x=11, y=22)
index(y=22, x=22)

解决方案一:失败,优化见↓↓↓

问题:没有修改被装饰对象的调用方式,但是修改了其源代码

import time

def index(x,y):
    start_time = time.time()
    time.sleep(1)
    print('index函数打印:%s %s'%(x, y))
    stop_time = time.time()
    print('函数index运行时间为:{}'.format(stop_time-start_time))

index(111,222)

解决方案二:失败,优化见↓↓↓

问题:没有修改被装饰对象的调用方式,也没有修改了其源代码,并且加上了新功能
但是代码冗余,index被调用的位置有很多,每个位置都上下夹击的写

import time

def index(x,y):
    print('index函数打印:%s %s'%(x, y))

start_time = time.time()
index(11,22)
stop_time = time.time()
print(f'函数index运行时间为{stop_time-start_time}')

start_time = time.time()
index(333,444)
stop_time = time.time()
print(f'函数index运行时间为{stop_time-start_time}')

解决方案三:失败,优化见↓↓↓

问题:解决了方案二代码冗余问题,但带来一个新问题即函数的调用方式改变了

import time

def index(x,y):
    time.sleep(1)
    print('index函数打印:%s %s'%(x, y))

def wrapper():
    start_time = time.time()
    index(111, 222)  # 这里参数写死了
    stop_time = time.time()
    print(f'函数index运行时间为{stop_time-start_time}')

wrapper()
方案三的优化一:将index的参数写活了
import time

def index(x,y):
    time.sleep(1)
    print('index函数打印:%s %s'%(x, y))

def wrapper(*args,**kwargs): # 接收参数,整合
    start_time = time.time()
    index(*args,**kwargs)  # 打散
    stop_time = time.time()
    print(f'函数index运行时间为{stop_time-start_time}')

wrapper(111,222)
wrapper(333,444)

在这里插入图片描述

方案三的优化二:在优化一的基础上把被装饰对象写活了

原来wrapper里面函数index名写死了,只能装饰index,其实可将函数名当做参数传给wrapper

import time

def index(x,y):
    time.sleep(1)
    print('index函数打印:%s %s'%(x, y))

def outter(func):
    def wrapper(*args,**kwargs): # 接收参数,整合
        start_time = time.time()
        func(*args,**kwargs)  # 打散
        stop_time = time.time()
        print(f'函数index运行时间为{stop_time-start_time}')
    return wrapper  # 返回wrapper函数的内存地址

# fff = outter(index) # 1、outter()返回的是wrapper函数内存地址,加()表示调用函数wrapper
                      # 2、其中fff可以自定义,也可定义成index;
# fff(111,222)
index = outter(index) 
index(111,222)

在这里插入图片描述

方案三的优化三:将wrapper做的跟被装饰对象一模一样,以假乱真

①如果需要获取装饰的函数中有返回值,怎么办?使用res接收。

import time

def index(x,y):
    time.sleep(1)
    print('index函数打印:%s %s'%(x, y))

def home(name):
    time.sleep(1)
    print('welcome %s to home page' %name)
    return 123

def outter(func):
    def wrapper(*args,**kwargs): # 接收参数,整合
        start_time = time.time()
        res = func(*args,**kwargs)  # 先打散,再执行func函数,最后将返回值给res
        stop_time = time.time()
        print(f'函数index运行时间为{stop_time-start_time}')
        return res
    return wrapper 

home = outter(home)  # 偷梁换柱:home这个名字指向的wrapper函数的内存地址
res = home('linhui')
print('res==》', res)

在这里插入图片描述

4. 语法糖:让你开心的语法

【注】:需要在定义函数前先定义好装饰器,如果调用的函数在装饰器前面,运行程序时会提示名为’xxx’的装饰器未定义

import time

# 装饰器
def timmer(func):
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print(stop - start)
        return res
    return wrapper

# # 在被装饰对象正上方的单独一行写@装饰器名字
@timmer  # index=timmer(index)
def index(x,y,z):
    time.sleep(1)
    print('index %s %s %s' %(x,y,z))

@timmer # home=timmer(ome)
def home(name):
    time.sleep(1)
    print('welcome %s to home page' %name)

index(x=1,y=2,z=3)
home('大飞飞')

5. 总结无参装饰器模板

def outter(func):
    def wrapper(*args,**kwargs):
        # 1、调用原函数
        # 2、为其增加新功能
        res=func(*args,**kwargs)
        return res
    return wrapper

6. 叠加多个装饰器,加载顺序与运行顺序

@deco1  # index=deco1(deco2.wrapper的内存地址)
@deco2  # deco2.wrapper的内存地址=deco2(deco3.wrapper的内存地址)
@deco3  # deco3.wrapper的内存地址=deco3(index)
def index():
    pass

7. 案例讲解:@语法糖底层逻辑

注:为有参装饰器做知识储备

【案例1说明】:@print==>等价于index = print(index),即print(index)打印的是index的函数内存地址

'''案例1:举例@print作为装饰器'''

@print    # @print==>等价于index = print(index)
def index(x,y):
    print(1111)
    pass

输出:
<function index at 0x7f2d6e3f01e0>

【案例2说明】:@print==>index = print(index),先打印index内存地址,然后将print(index)函数运行后的返回值赋给了index,这里的print()装饰器本身没有返回值,所以print(index)=None

'''案例2:举例@print作为装饰器'''

@print
def index(x,y):
    print(1111)
    pass

print(index)

输出:
function index at 0x7fa2763151e0>
None

【案例3说明】:
这里的@print(‘hello’) 等价于:
第一步:先运行print(hello),输出hello
第二步:print(hello)的返回结果为None
第三步:@print(‘hello’) 等价于 @None
第四步:@None 等价于 index=None(index) ,故最终执行报错

'''案例3:举例@print作为装饰器'''
@print('hello')
def index(x,y):
    print(1111)
    pass

hello
Traceback (most recent call last):
File “/home/xionglinhui/my_python_project/temp.py”, line 15, in
@print(‘hello’)
TypeError: ‘NoneType’ object is not callable

8.wraps装饰器介绍

举例说明:列举不调用装饰器和调用装饰器时函数index的属性区别,通过案例1和案例2引出进一步被装饰对象在被装饰器调用前和调用后的区别,从而优化下装饰器代码,最终目的是将wrapper函数做的和原函数一模一样。

''' 案例1 没有调用装饰器,查看原函数的函数名和功能说明 '''


def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res

    return wrapper

def index(x, y):
    '''我是index,我的功能是打印参数'''
    print(f'打印index的x,y ==> {x} {y}')
    return '我是index返回值'

# 注释掉@outter装饰器,查看index函数地址和函数功能说明
index = outter(index)
print(index)  # 输出:<function outter.<locals>.wrapper at 0x00000263568EF910>
# 翻译即是函数outter的局部函数wrapper的内存地址
index(666, 999)  # 打印index的x,y ==> 666 999

print(index.__name__)  # 获取函数index的名字,即index
print(index.__doc__)  # 获取函数index的功能说明,即'''xxx'''里面的内容,即“我是index,我的功能是打印参数”
''' 案例2 调用装饰器,查看原函数的函数名和功能说明 '''
def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

@outter
def index(x, y):
    '''我是index,我的功能是打印参数'''
    print(f'打印index的x,y ==> {x} {y}')
    return '我是index返回值'

#调用装饰器@outter后,查看index函数地址和函数功能说明
print(index)  # <function outter.<locals>.wrapper at 0x0000022FC505F880>
print(index.__name__)   # wrapper
print(index.__doc__)   # None

分析

  1. index(或定义为xxx) = outter(index)相当于把wrapper函数的内存地址赋给index,这里的index可以是任意变量;
  2. 这里的index实际上是wrapper,当我们查看index.__name__函数名和index.__doc__功能说明时,实际上查看的时wrapper的功能说明,与我们的偷梁换柱目的还差一点。

解决思路:优化方案请见《案例3》

  1. index调用outter装饰器后,index.__name__值为wrapper,
    而index调用outter装饰器前,index.__name__值为index,
    所以在调用装饰器前,即 定义阶段 需要将原函数index.__name____的名字传给wrapper.__name__名字,即:见下图
wrapper.__name__ = func.__name__   #函数名
wrapper.__doc__ = func.__doc__     #函数功能说明,即注释
''' 案例3 将index函数的属性值赋给wrapper,即例如wrapper.__name__ = func.__name__'''

def outter(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    '''我是wrapper函数内的'''
    #模板:
    #函数wrapper.__name__  = 被装饰对象(原函数).__name__
    #函数wrapper.__doc__  = 被装饰对象(原函数).__doc__
    wrapper.__name__ = func.__name__  ##代码优化部分
    wrapper.__doc__ = func.__doc__
    return wrapper

@outter
def index(x, y):
    '''我是index,我的功能ABCDEFG是打印参数'''
    print(f'打印index的x,y ==> {x} {y}')
    return '我是index返回值'

#优化一:将index函数的属性值赋给wrapper
print(index)  # <function outter.<locals>.wrapper at 0x0000022FC505F880>
print(index.__name__)   # index
print(index.__doc__)   # 我是index,我的功能ABCDEFG是打印参数

存在的问题和解决方法
1.问题:函数的属性有很多,见下图,如果将每个属性都传给wrapper,那么代码会过于冗余。
2.解决方法:解释器考虑到这一点,特定封装了wraps装饰器用于解决该问题,该函数封装在functools,调用方式为from functools import wraps,见**《案例4》**
在这里插入图片描述

''' 案例4 使用wraps内置装饰器,优化代码'''

from functools import wraps

def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        '''我是wrapper函数内的'''
        res = func(*args, **kwargs)
        return res
    return wrapper

@outter
def index(x, y):
    '''我是index,我的功能ABCDEFG是打印参数'''
    print(f'打印index的x,y ==> {x} {y}')
    return '我是index返回值'

#优化二:使用wraps内置装饰器,优化代码
print(index)  # <function index at 0x000001F86E5C65F0>
print(index.__name__)   # index
print(index.__doc__)   # 我是index,我的功能ABCDEFG是打印参数

9. 思考:为什么会有两次时间计算?

1.要求:详细说明代码执行流程
==========================
2.分析过程: print(outter(index)(11,22))
①outter(index) ==> 注意哈,这里的index就是被装饰对象index函数的内存地址 ;outter(index) ==>等价于index = wrapper函数的内存地址
②wrapper(11,22) ==>执行wrapper函数,当遇到func(当前的func就是被装饰对象index)时代码会跳转到index()函数,识别到了装饰器@outter,此时会优先执行@outter,@outter等价于outter(被装饰对象index),先去执行一遍wrapper函数且执行index函数内部代码
③跳过装饰器,继续执行步骤2中的wrapper(11,22)

'''案例:存在装饰器却不直接使用装饰器,分析输出结果!!'''

import time

def outter(func):
    def wrapper(*args,**kwargs):
        start = time.time()
        res = func(*args,**kwargs)
        end = time.time()
        print(f'代码运行时间:{end-start}')
        return res
    return wrapper

@outter  # index=outter(index)=wrapper函数内存地址
def index(x, y):
    time.sleep(1)
    print(f'打印index的输出:{x} {y}')
    return 12345678

print(outter(index)(11,22))

在这里插入图片描述

二、有参装饰器

1. 知识储备

① 由于语法糖@的限制,outter函数只能有一个参数,并且该参数只用来接收被装饰对象的内存地址

② 案例需求:实现一个用来为被装饰对象添加认证功能的装饰器。认证成功后,如果文件类型为file,则执行func;如果文件类型为mysql,则打印’基于mysql的验证’;;如果文件类型为ldap,则打印’基于ldap的验证’。

def outter(func):
    # func = 函数的内存地址
    def wrapper(*args,**kwargs):
        res=func(*args,**kwargs)
        return res
    return wrapper

# @outter # index=outter(index) # index=>wrapper
@outter # outter(index)
def index(x,y):
    print(x,y)

偷梁换柱之后,我们的正真目的是:↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
1.index的参数什么样子,wrapper的参数就应该什么样子
2.index的返回值什么样子,wrapper的返回值就应该什么样子
3.index的属性什么样子,wrapper的属性就应该什么样子==》from functools import wraps

2.不使用有参装饰器的山炮玩法 <案例1>

'''案例1:纯函数的调用'''
def auth(func,db_type):
    def wrapper(*args, **kwargs):
        name=input('your name>>>: ').strip()
        pwd=input('your password>>>: ').strip()

        if db_type == 'file':
            print('基于文件的验证')
            if name == 'dafeifei' and pwd == '123':
                res = func(*args, **kwargs)
                return res
            else:
                print('user or password error')
        elif db_type == 'mysql':
            print('基于mysql的验证')
            res = func(*args, **kwargs)
            return res

        elif db_type == 'ldap':
            print('基于ldap的验证')
        else:
            print('不支持该db_type')

    return wrapper

# @auth  # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))

# @auth # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)

# @auth # 账号密码的来源是ldap
def transfer():
    print('transfer')
    
# index=auth(index,'file')
# index(11,22)
home=auth(home,'mysql')
home('dafeifei')
# transfer=auth(transfer,'ldap')
# transfer()

3.使用有参装饰器的山炮玩法 <案例2>

'''案例2:deco=auth(db_type='file'),'''
def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):
            name=input('your name>>>: ').strip()
            pwd=input('your password>>>: ').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')

        return wrapper
    return deco

deco=auth(db_type='file')
@deco # 账号密码的来源是文件
def index(x,y):
    print('index->>%s:%s' %(x,y))

deco=auth(db_type='mysql')
@deco # 账号密码的来源是数据库
def home(name):
    print('home->>%s' %name)

deco=auth(db_type='ldap')
@deco # 账号密码的来源是ldap
def transfer():
    print('transfer')


index(1,2)
# home('egon')
# transfer()

3.使用有参装饰器的最好玩法 <案例3>

【解释】:@auth(db_type=‘file’) ,见到()表示函数调用,先忽略@
第一步:auth(db_type=‘file’) ==>调用auth()返回的是deco函数的内存地址,即 index = deco的内存地址 ,得到了@deco,包含了对外部作用域名字db_type的引用,@deco的语法意义与无参装饰器一样;
第二步:@deco ==> 等价于index = deco(index),调用deco(index)返回的是wrapper函数的内存地址,即index = wrapper函数地址

'''案例3:语法糖'''

def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):
            name = input('your name>>>: ').strip()
            pwd = input('your password>>>: ').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'egon' and pwd == '123':
                    res = func(*args, **kwargs)  # index(1,2)
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')
        return wrapper
    return deco


@auth(db_type='file')  # @deco # index=deco(index) # index=wrapper
def index(x, y):
    print('index->>%s:%s' % (x, y))

@auth(db_type='mysql')  # @deco # home=deco(home) # home=wrapper
def home(name):
    print('home->>%s' % name)


@auth(db_type='ldap')  # 账号密码的来源是ldap
def transfer():
    print('transfer')

index(1, 2)
# home('egon')
# transfer()

4.有参装饰器模板

'''******有参装饰器模板******'''

def 有参装饰器(x,y,z):
    def outter(func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outter

@有参装饰器(1,y=2,z=3)
def 被装饰对象():
    pass

三、叠加装饰器

记住结论!记住结论!记住结论!
【叠加装饰器的加载顺序】:自下而上(了解)
【叠加装饰器的执行顺序】:自上而下(重点),最下面那个装饰器调用的才是原函数

↑↑↑通过案例引出结论,结论见上方↑↑↑

'''案例:通过案例详细了解下叠加装饰器的加载和执行逻辑,最后我们只需记住结论即可'''

def deco1(func1): #func1 = wrapper2的内存地址
    def wrapper1(*args, **kwargs):
        print('正在运行===>deco1.wrapper1')
        res1 = func1(*args, **kwargs)
        print('已结束===>deco1.wrapper1')
        return res1
    return wrapper1

def deco2(func2): # func2 = wrapper3的内存地址
    def wrapper2(*args, **kwargs):
        print('正在运行===>deco2.wrapper2')
        res2 = func2(*args, **kwargs)
        print('已结束===>deco2.wrapper2')
        return res2
    return wrapper2

def deco3(x): # @deco3(111)==>得到了outter函数的内存地址,@+outter语法,含义是通过调用outter函数将正下方的函数地址传入,得到的返回值赋值给原函数名
    def outter(func3):    # index = outter(index),故func3=被装饰对象index函数的内存地址
        def wrapper3(*args, **kwargs):
            print('正在运行===>deco3.wrapper3')
            res3 = func3(*args, **kwargs)
            print('已结束===>deco3.wrapper3')
            return res3
        return wrapper3
    return outter

#加载顺序:自下而上(了解)
@deco1          #1、@deco1 等价于==>index=deco1(wrapper2的内存地址)=wrapper1的内存地址,即index=wrapper1的内存地址
@deco2          #1、@deco2 等价于==>index=deco2(wrapper3的内存地址)=wrapper2的内存地址,即index=wrapper2的内存地址
@deco3(111)     #1、deco3(111)等价于==> index = outter  2、@outter 等价于==> index = outter3(func)= wrapper3的内存地址 ,即index = wrapper3的内存地址
def index(x,y):
    print('from index %s:%s' %(x,y))
    return '我是最原始的index'


'''综上所述,index函数的内存地址被换成了wrapper1函数的内存地址,打印index查看'''
print(index)   # >输出:<function deco1.<locals>.wrapper1 at 0x7f409d9e01e0>

# 执行顺序自上而下的,即wraper1-》wrapper2-》wrapper3(记结论)
a = index(666,888)  # 等价==>wrapper1(666, 888) 
print('=**==**==**==**==**==**=')
print(a)

输出:<function deco1..wrapper1 at 0x7fbb3a0051e0>
正在运行===>deco1.wrapper1
正在运行===>deco2.wrapper2
正在运行===>deco3.wrapper3
from index 666:888
已结束===>deco3.wrapper3
已结束===>deco2.wrapper2
已结束===>deco1.wrapper1
===**====**=
我是最原始的index

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值