• python装饰器详解


    python 装饰器

    模拟银行存款取款

    def deposit():
        print('存款中')
    def withdraw():
        print('取款中')
    button=1
    if button==1:
        deposit()
    else:
        withdraw()
    
    存款中
    

    不对,还得加上密码验证功能

    def deposit():
        print('存款中...')
    def withdraw():
        print('取款中...')
    def check_passwd():
        print('密码验证中...')
    button=1
    if button==1:
        check_passwd()
        deposit()
    else:
        check_passwd()
        withdraw()
    
    密码验证中...
    存款中...
    

    代码冗余度高,每次进行操作都要在前面加上 check_passwd(),除了存款取款,还有查询,转账等功能,也要加上check_passwd(),代码的复用性不好

    我们试着把check_passwd() 加到操作内部

    def check_passwd():
        print('密码验证中...')
    def deposit():
        check_passwd()
        print('存款中...')
    def withdraw():
        check_passwd()
        print('取款中...')
    
    button=1
    if button==1:
        deposit()
    else:
        withdraw()
    
    密码验证中...
    存款中...
    

    函数内部又实现另外一个函数,违背的代码的开闭原则,对扩展开放,对修改封闭.而且一个函数最好只有一个功能,即单一职责原则

    为了在不改变原函数的前提下增加密码验证功能,我们把存款,全款等功能加到密码验证函数里.

    def deposit():
        print('存款中...')
    def withdraw():
        print('取款中...')
        
    def check_passwd(func):
        print('密码验证中...')
        func()
    button=1
    if button==1:
        check_passwd(deposit)
    else:
        check_passwd(withdraw)
    
    密码验证中...
    存款中...
    

    原函数没有变,但是问题已经解决,还可以继续扩展新的银行业务功能,转账,查询等.
    但是,业务逻辑代码已经更改, 不再是调用withdraw()了.

    有没有什么办法,在不改变原函数,也不改变原函数调用方法的情况下扩展原函数的功能呢?

    这就是python中的装饰器

    def deposit():
        print('存款中')
    def withdraw():
        print('取款中')
    

    只有中间这段可以改

    def check_passwd(func):
        pass
    
    button=1
    if button==1:
        deposit()
    else:
        withdraw()
    
    def deposit():
        print('存款中')
    def withdraw():
        print('取款中')
    
    def check_passwd(func):
        def wrapper():
            print('密码验证中')
            func()
        return wrapper
    
    deposit = check_passwd(deposit)
    withdraw = check_passwd(withdraw)
    
    
    button=1
    if button==1:
        deposit()
    else:
        withdraw()
    
    
    密码验证中
    存款中
    

    check_passwd 就是 deposit 和 withdraw 的装饰器函数, 将原有的函数装饰了一下

    语法糖 @

    def check_passwd(func):
        def wrapper():
            print('密码验证中')
            func()
        return wrapper
    @check_passwd
    def deposit(): ##相当于check_passwd(deposit)
        print('存款中')
        
    @check_passwd
    def withdraw():
        print('取款中')
    
    button=1
    if button==1:
        deposit()
    else:
        withdraw()
    
    密码验证中
    存款中
    

    装饰器的有参函数

    def decoration(func):
        def wrapper():
            print('log:%s is called'%func.__name__)
            func()
        return wrapper
            
    @decoration
    def greet(name):
        print("hello,%s"%name)
    
    greet('Tom')
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-15-bd91aac010af> in <module>
          9     print("hello,%s"%name)
         10 
    ---> 11 greet('Tom')
    
    
    TypeError: wrapper() takes 0 positional arguments but 1 was given
    

    被装饰的函数没有传参,表面上调用的是greet,实际上greet被装饰成了wrapper,再来改一下代码

    def decoration(func):
        def wrapper(name):
            print('log:%s is called'%func.__name__)
            func(name)
        return wrapper
            
    @decoration
    def greet(name):
        print("hello,%s"%name)
    
    greet('Tom')
    
    log:greet is called
    hello,Tom
    

    为了使装饰器能够装饰 有各种参数的函数,我们来修改代码为万金油型的传参方式

    def decoration(func):
        def wrapper(*args,**kw):
            print('log:%s is called'%func.__name__)
            func(*args,**kw)
        return wrapper
            
    @decoration
    def greet(name):
        print("hello,%s"%name)
    @decoration
    def greets(*people):
        print("hello,%s"%' and '.join(people))
    greet('Tom')
    greets('John','Jack','Lina','Scofield')
    
    log:greet is called
    hello,Tom
    log:greets is called
    hello,John and Jack and Lina and Scofield
    

    函数有返回值,又该怎么装饰呢?

    def decoration(func):
        def wrapper(*args,**kw):
            print('log:%s is called'%func.__name__)
            func(*args,**kw)
        return wrapper
    @decoration
    def fetch(a):
        return a
    @decoration
    def myprint(a):
        print(a)
    r1 =  fetch(100)
    r2 =  myprint(100)
    print(r1,r2)
    
    log:fetch is called
    log:myprint is called
    100
    None None
    

    不对啊,都是None, 因为实际调用的wrapper根本没有返回值

    我们还需要修改wrapper,使它的返回值与被包裹的函数一致

    def decoration(func):
        def wrapper(*args,**kw):
            print('log:%s is called'%func.__name__)
            return func(*args,**kw)
        return wrapper
    @decoration
    def fetch(a):
        return a
    @decoration
    def myprint(a):
        print(a)
    r1 =  fetch(100)
    r2 =  myprint(100)
    print(r1,r2)
    
    log:fetch is called
    log:myprint is called
    100
    100 None
    

    双重语法糖 双层装饰/包裹

    def decorateStar(func):
        def wrapper(*args,**kw):
            print("*"*10)
            return func(*args,**kw)
        return wrapper
    
    def decorateEqual(func):
        def wrapper(*args,**kw):
            print("="*10)
            return func(*args,**kw)
        return wrapper
    @decorateStar
    @decorateEqual
    def myprint(a):
        print(a)
    myprint(100)
    
    **********
    ==========
    100
    

    相当于如下,外层的装饰器先运行

    def decorateStar(func):
        def wrapper(*args,**kw):
            print("*"*10)
            return func(*args,**kw)
        return wrapper
    
    def decorateEqual(func):
        def wrapper(*args,**kw):
            print("="*10)
            return func(*args,**kw)
        return wrapper
    def myprint(a):
        print(a)
    decorateStar(decorateEqual(myprint))(100)
    
    **********
    ==========
    100
    

    但是,上面的两次语法糖太冗余了,我们可以使用带参数的语法糖

    def decorateChar(Char):
        def decorate(func):
            def wrapper(*args,**kw):
                print(Char*10)
                return func(*args,**kw)
            return wrapper
        return decorate
    
    @decorateChar("*")
    @decorateChar("=")
    def myprint(a):
        print(a)
    myprint(100)
    
    **********
    ==========
    100
    

    带参数的语法糖,因为有了参数,就多需要一层包裹,相当于decorateChar("*")(myprint)

  • 相关阅读:
    时间戳(1532249295.179) 转日期格式(2018/07/22 16:48:15 179)
    iscroll.js右侧可滑动的菜单,点击每个菜单都会出现本菜单的详情
    canvas绘制的文字如何换行
    移动端H5页面禁止长按复制和去掉点击时高亮
    一列宽度不缩放,一列宽度弹性缩放,且超出后显示省略号
    js钩子机制(hook)
    mCustomScrollbar.js 漂亮的滚动条插件 适应内容自动更新
    axios.js 实例 -----$.ajax的替代方案
    用 async/await 来处理异步实例
    C#入门经典第18章-WEB编程
  • 原文地址:https://www.cnblogs.com/ShawSpring/p/10646506.html
Copyright © 2020-2023  润新知