Python基础(十二): 装饰器

装饰器使用的是函数中的闭包功能

一、装饰器

装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码

二、语法

  • 语法
@装饰器
def 被装饰的函数():
    code

三、举例

def name(func):
    def inner():
        print("zhangsan")
        func()
    return inner

@name
def say():
    print("hello world")

say()

# 打印结果: 
zhangsan
hellow world

四、讲解

  • say函数定义上添加@name 等价于 say = name(say)
@name
def say():
    print("hello world")
  • 等价于:
def say():
    print("hello world")
say = name(say)

五、注意: 装饰器的执行时间是立即实行

  • 当代码执行到@name时, 实际上函数name已经被执行了, 并且传入的参数func的值是say, 执行后say指向新的函数, 此时变成下面的代码:
def inner()
    print("zhangsan")
    say()
  • 结果: say没有被调用, 依然打印 xxxxxxx
def name(func):
    print("xxxxxxx")
    def inner():
        print("zhangsan")
        func()
    return inner

@name
def say():
    print("hello world")

# 打印结果: 
xxxxxxx

六、案例: 添加装饰器的过程

现有模拟案例如下:

  1. 已知有两个功能函数, 分别是发图片和发说说

    def ftp():
        print("发图片")
    
    def fss():
        print("发说说")
    
  2. 在页面上有两个按钮, 点击第一个按钮时发出片, 点击第二个按钮时发说说

    btnIndex = 1
    if btnIndex == 1:
        ftp()
    else:
        fss()
    

    注意: 此处是模拟, 当btnIndex的值为1时, 认为点击第一个按钮, 值为2时, 认为点击第二个按钮

  3. 由于发图片和发说说都是用户登陆后才能操作, 所以在发图片和发说说前, 都需要进行登录验证

    btnIndex = 1
    if btnIndex == 1:
        print("登录验证")
        ftp()
    else:
        print("登录验证")
        fss()
    

    注意: 此处使用print("登录验证")表示了登录验证的功能, 真实项目中替换为具体的代码实现即可

  4. 上面已经解决了在发图片发说说前的登录验证功能

    但是却有一个问题, 那就是发图片和发说说这两个功能, 也可以在当前项目中其他的地方使用

    而那时又需要做登录验证操作

    这样会造成代码冗余, 且不利于维护, 不利于阅读

    所以我们将登录验证功能换一个地方, 而逻辑代码不变, 如下

    def ftp():
        print("登录验证")
        print("发图片")
    
    def fss():
        print("登录验证")
        print("发说说")
        
    btnIndex = 1
    if btnIndex == 1:
        ftp()
    else:
        fss()
    
  5. 第4步中解决了在使用发图片和发说说两个功能时, 只需要直接调用函数即可, 函数内部已经实现了对登录的验证操作, 多个地方使用这两个功能也只需要直接调用即可

    但是, 这里有一个问题, 我们的模拟代码print("登录验证")只有一句话, 而在实际操作过程中, 一个登录验证可能会有很多行的代码, 而发说说和发图片中都使用了这段代码, 此时会造成代码冗余

    所以, 在第四步的基础上, 将登录验证的代码抽取出来, 此时有如下代码

    def checkLogin():
        print("登录验证")
    
    def ftp():
        checkLogin()
        print("发图片")
    
    def fss():
        checkLogin()
        print("发说说")
    

    我们只需要在发图片和发说说的具体实现前, 加入登录验证函数的调用即可

  6. 在第5步中, 我们解决了登录验证代码过多, 可能导致第四步中, 发图片函数和发说说函数中登录验证代码过多的冗余问题

    看起来问题解决了, 但是却违背了"开放封闭"原则:

     1, 已经写好的代码, 尽可能不要修改
     2, 如果想要新增功能, 在原先代码基础上, 单独进行扩展
    

    并且违背了功能模块, 功能单一职责

    所以我们需要将发图片和发说说两个函数中的checkLogin(), 去掉, 并实现下面的函数

    def ftp():
        print("发图片")
    
    def fss():
        print("发说说")
    
    def checkLoginToFtp():
        print("登录验证")
        ftp()
        
    def checkLoginToFss():
        print("登录验证")
        fss()
    

    在业务逻辑中, 当按钮被点击时的调用如下

    btnIndex = 1
    if btnIndex == 1:
        checkLoginToFtp()
    else:
        checkLoginToFss()
    

    这样, 我们就在不改变ftp()fss()两个函数的基础上, 添加了登录验证功能

  7. 第6步中的代码会出现冗余现象, 比如checkLoginToFtpcheckLoginToFss都调用了print("登录验证")

    这里对第6步中的代码进行简化

    def ftp():
        print("发图片")
    
    def fss():
        print("发说说")
    
    def checkLogin(func):
        print("登录验证")
        func()
    

    调用时

    btnIndex = 1
    if btnIndex == 1:
        checkLogin(ftp)
    else:
        checkLogin(fss)
    

    此时, 可以直接使用checkLogin函数, 对在指定函数前添加登录验证了

  8. 第7步中, 对代码进行了优化, 可以自由的在任何函数前都添加登录验证操作, 且不会出现代码冗余的情况

    但是, 这里修改了第2步中, 逻辑代码部分的实现内容

    现在要做的是, 在第1步和第2步中的代码都不变的基础上, 通过添加额外的代码, 实现登录验证的操作

    已知: 两个功能函数 ftpfss, 而这两个函数的函数名, 实质上就是变量, 那么我们通过将函数名重新赋值的方式, 修改函数指向

    def checkLogin(func):
        print("登录验证")
        func()
        
    ftp = checkLogin(ftp)
    fss = checkLogin(fss)
    

    此时, ftpfss的值已经不是之前的函数, 而是checkLogin(ftp)checkLogin(fss)的返回值, 即 None

  9. 在第8步中, 我们修改了变量ftpfss的指向, 此时两个变量的值都是 None

    我们知道, 第2步中的ftpfss是两个不同的函数, 所以才能被调用, 而此时的ftpfss已经无法像函数一样被调用

    我们需要修改checkLogin函数, 让他的返回值依然是函数

    def checkLogin(func):
        def inner():
            print("登录验证")
            func()
        return inner
    

    这样, 我们在给ftpfss赋值

    ftp = checkLogin(ftp)
    fss = checkLogin(fss)
    

    此时,

    ftp的值是函数

    def inner():
        print("登录验证")
        ftp()
    

    fss的值是函数

    def inner():
        print("登录验证")
        fss()
    

    此时ftpfss可以如下调用

    btnIndex = 1
    if btnIndex == 1:
        ftp()
    else:
        fss()
    
  10. 我们看看现在的代码

    def checkLogin(func):
        def inner():
            print("登录验证")
            func()
        return inner
        
    def ftp():
        print("发图片")
    
    def fss():
        print("发说说")
        
    ftp = checkLogin(ftp)
    fss = checkLogin(fss)
    
    btnIndex = 1
    if btnIndex == 1:
        ftp()
    else:
        fss()
    

    此时, 通过加入的代码, 已经将ftpfss两个函数指向了新的函数

    而这个新函数在实现原有代码之前, 加入了登录验证功能

    此时: 我们实现了, 在1和2代码不变的情况下, 加入新代码, 增加了新的功能

  11. 在上述代码中, 有ftp = checkLogin(ftp)fss = checkLogin(fss)两个重新赋值的操作, 在Python中, 有一个简单写法, 与这两句等价

    def checkLogin(func):
        def inner():
            print("登录验证")
            func()
        return inner
        
    @checkLogin
    def ftp():
        print("发图片")
        
    @checkLogin
    def fss():
        print("发说说")
    

    这种简易方法, 就是语法糖, 即能用简单的写法, 写出复杂的功能, 如同糖一样甜

    上面的代码中ftp函数上添加@checkLogin 等价于 ftp = checkLogin(ftp)

  12. 第11步中的 checkLogin函数实现@checkLogin, 就是装饰器

    装饰器: 在函数名以及函数体不改变的前提下, 给一个函数附加一些额外代码

总结: 通过闭包的形式, 将原有函数名重新赋值一个新的函数, 这个新函数中调用了原有函数, 并在原有函数的基础上添加了新的内容, 当通过该函数名调用时, 会执行更多的功能, 这就是装饰器的作用

七、进阶

1、装饰器叠加
def printStar(func):
    print("1111")
    def inner():
        print("*" * 30)
        func()
    return inner

def printLine(func):
    print("2222")
    def inner():
        print("-" * 30)
        func()
    return inner

@printStar
@printLine
def name():
    print("name")

# 打印结果
2222
1111
  • 可以得出, 当多个装饰器同时修饰同一个函数式, 距离函数最新的装饰器最先被加载
name = printLine(name)
name = printStar(name)
  • 此时我们直接调用name函数
def printStar(func):
    def inner():
        print("*" * 30)
        func()
    return inner

def printLine(func):
    def inner():
        print("-" * 30)
        func()
    return inner

@printStar
@printLine
def name():
    print("name")

name()      # 调用

# 打印结果:
******************************
------------------------------
name
  • 由上面的打印可以知道, 距离函数最远的装饰器, 在函数被调用时, 最先被执行
2、对有参函数进行装饰
  • 装饰目标:
def sum(num1, num2)
    print(num1, num2)
  • 装饰器
def decorator(func):
    def inner(*args, **kwargs):
        func(*args, **kwargs)
    return inner
  • 结果:
def decorator(func):
    def inner(*args, **kwargs):
        print("-" * 20)
        func(*args, **kwargs)
    return inner

@decorator
def sum(num1, num2):
    print(num1, num2)
    
sum(1, 2)

# 打印结果:
--------------------
3
3、对有返回值的函数进行装饰, 无论什么场景, 保持函数返回值一致
  • 装饰目标:
def sum(num1, num2):
    return num1 + num2
  • 装饰器
def decorator(func):
    def inner(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return inner
  • 结果
def decorator(func):
    def inner(*args, **kwargs):
        print("-" * 20)
        result = func(*args, **kwargs)
        return result
    return inner

@decorator
def sum(num1, num2):
    return num1 + num2

print(sum(num1 = 1, num2 = 2))

# 打印结果:
--------------------
3
4. 带有参数的装饰器
  • 装饰目标
def name1():
    print("zhangsan")
    
def name2():
    print("lisi")
  • 目标: 在打印zhangsan之前, 先打印20个*, 在打印lisi之前, 先打印20个-

  • 装饰器

def symbol(char):
    def decorator(func):
        def inner():
            print(char * 20)
            func()
        return inner
    return decorator
  • 结果:
def symbol(char):
    def decorator(func):
        def inner():
            print(char * 20)
            func()
        return inner
    return decorator

@symbol("*")
def name1():
    print("zhangsan")


@symbol("-")
def name2():
    print("lisi")

name1()
name2()

# 打印结果: 
********************
zhangsan
--------------------
lisi

通过@装饰器(参数)的方式, 调用这个函数, 并传递参数, 并把返回值, 再次当做装饰器进行使用

先计算@后面的内容, 并把这个内容当做是装饰器

注意: 装饰器@xxxx, 后面的xxxx的结果必须是一个函数, 并且这个函数只能拥有一个参数, 用来接收函数名

执行@symbol("*")时, 会先执行symbol("*"), 然后将执行结果作为装饰器, 即decorator

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,429评论 1 24
  • 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,...
    chen_000阅读 1,360评论 0 3
  • 1.大学中需要计划自己的时间。合理安排时间。让大学生活变得充实。 2.reference 3. But a lar...
    侯淑洁阅读 226评论 1 0
  • 又是一天啦,今天吃得特别好,开心嘻嘻,绿豆稀饭,馍馍,中午的扯面皮,晚上炒辣椒瘪和南瓜饼好吃哭了呀!!! 然后我的...
    一只菠萝包阅读 119评论 0 0
  • 新零售、新市集、百盛优客、餐饮大逆转……这么多行业热点话题,你关注哪个? http://maxonesoft.co...
    波波叔阅读 167评论 0 0