Python基础(十二): 装饰器

2018-03-04  本文已影响7人  伯wen

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

一、装饰器

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

二、语法

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

三、举例

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

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

say()

# 打印结果: 
zhangsan
hellow world

四、讲解

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

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

def inner()
    print("zhangsan")
    say()
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)
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")
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

上一篇下一篇

猜你喜欢

热点阅读