• 装饰器详解


    装饰器(Decorator)本质是函数,功能是为其他函数添加附加功能,定义一个装饰器需要满足以下两个原则:

    • 不修改被修饰函数源代码(开放封闭原则)
    • 不修改被修饰函数的调用方式

    装饰器 = 高阶函数 + 函数嵌套 + 闭包

    1. 高阶函数

    高阶函数定义:

    • 函数接收的参数是一个函数
    • 函数的返回值是一个函数

    满足以上任意一个条件就是高阶函数。

    def calc():
    	print('calc 运行完毕~')
    f = calc
    f()
    

    由于函数也是对象,因此函数也可以赋值给一个变量,调用这个变量就是调用函数。函数对象有一个 __name__属性,用于查看函数的名字:

    calc.__name__
    f.__name__
    
    'calc'
    'calc'
    

    需求:

    在不修改 calc 源代码的前提下,为 calc函数添加一个代码计时功能(计算 calc 运行时间)。

    1.1 把函数当做参数传给另一个函数

    使用高阶函数模拟装饰器实现calc计时功能。

    import time
    
    def timmer(func):
        start_time = time.time()
        func()
        stop_time = time.time()
        print('函数 %s 运行时间为:%s' % (func.__name__, stop_time - start_time))
        
    def calc():
        time.sleep(2)
        print('calc 运行完毕~')
    
    timmer(calc)
    
    calc 运行完毕~
    calc 运行时间为:2.0011146068573
    

    虽然为 calc 增加了计时功能,但 calc 原来的执行方式是 calc(),而现在是调用高阶函数timmer(calc),改变了调用方式。

    1.2 函数的返回值是函数

    import time
    
    def timmer(func):		# func: calc
        start_time = time.time()
        func()				# func(): calc()
        stop_time = time.time()
        print('函数 %s 运行时间为:%s' % (func.__name__, stop_time - start_time))
        return func			# func: calc (<function calc at 0x00000000052D6D90>) 内存地址
        
    def calc():
        time.sleep(2)
        print('calc 运行完毕~')
    
    calc = timmer(calc)    # calc = timmer(calc): calc = <function calc at 0x00000000052D6D90>
    calc()
    
    calc 运行完毕~
    calc 的运行时间是:2.0001144409179688
    calc 运行完毕~
    

    没有改变 calc 的调用方式,但也没为其添加新功能。

    1.3 总结

    使用高阶函数实现装饰器功能 :

    • 函数接收的参数是一个函数名
      • **作用:**在不修改函数源代码的前提下,为函数添加新功能。
      • **不足:**会改变函数的调用方式
    • 函数返回值是一个函数名
      • **作用:**不修改函数调用方式
      • **不足:**不能添加新功能

    2. 函数嵌套

    函数中嵌套另一个函数

    def father(name):
        print('I am %s' % name)
        def son():
            print('My father is %s' % name)
            def grandson():
                print('My grandfather is %s' % name)
            grandson()
        son()
    father('tom')
    
    I am tom
    My father is tom
    My grandfather is tom
    

    3. 闭包

    闭包也是函数嵌套,如果在一个内部函数里调用外部作用域(不是全局作用域)的变量,那么这个内部函数就是闭包(closure)

    def father(name):
        print('I am %s' % name)
        def son():
            name = 'john'
            def grandson():
                print('My father is %s' % name)
            grandson()
        son()
    father('tom')
    
    I am tom
    My father is john
    

    内部函数 grandson 调用了它的外部函数 son 的变量 name='john',那么 grandson就是一个闭包。

    4. 无参数装饰器

    无参数装饰器 = 高阶函数 + 函数嵌套

    4.1 基本框架

    # 实现一个装饰器的基本框架
    def timmer(func):
        def wrapper():
            func()
        return wrapper
    

    4.2 加上参数

    def timmer(func):
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
        return wrapper
    

    4.3 加上功能

    import time
    
    def timmer(func):
        def wrapper(*args, **kwargs):
            """计时功能"""
            start_time = time.time()
            func(*args, **kwargs)
            stop_time = time.time()
            print('函数 %s 运行时间:%s' % (func, stop_time - start_time))
        return wrapper
    

    4.4 加上返回值

    import time
    
    def timmer(func):
        def wrapper(*args, **kargs):
            start_time = time.time()
            res = func(*args, **kwargs)
            stop_time = time.time()
            print('函数 %s 运行时间:%s' % (func, stop_time - start_time))
            return res
        return wrapper
    

    4.5 使用装饰器

    def calc():
        time.sleep(2)
        print('calc 运行完毕~')
        return 'calc 返回值'
    calc = timmer(calc)
    calc()
    

    4.6 语法糖 @

    @timmer        # 相当于 calc = timmer(calc)
    def calc():
        time.sleep(2)
        print('calc 运行完毕~')
    calc()
    

    4.7 示例

    使用无参数装饰器,为 calc添加计时功能(统计 calc代码运行时间)

    import timmer
    
    def timmer(func):				# func: calc
        """装饰器函数"""
        def wrapper(*args, **kwargs):		# args:<class 'tuple'>:('rose', 18, 'female')
            start_time = time.time()		# kwargs={}
            res = func(*args, **kwargs)		# res: 'calc 返回值'
            stop_time = time.time()
            print('函数 %s 运行时间:%s' % (func.__name__, stop_time - start_time))
            return res       # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
        return wrapper
    
    @timmer					# @timmer: calc=timmer(calc)  ——>calc = <function wrapper at 0x00000000052D6D90>
    def calc(name, age, gender):
        """被修饰函数"""
        time.sleep(2)
        print('calc 运行完毕~')
        print('名字:%s,年龄:%d,性别:%s' % (name, age, gender))
        return 'calc 返回值'
    
    s = calc('rose', 18, 'female')		# 相当于执行 s = wrapper('rose', 18, 'female')
    print(s)    
    
    calc 运行完毕
    名字:rose,年龄:18,性别:female
    函数 calc 运行时间:2.0001144409179688
    calc 返回值
    

    由于 timmer() 是一个装饰器函数,返回一个函数 wrapper。所以 calc()函数仍然存在,只是现在同名的 calc 变量指向了新的函数,于是调用 calc() 执行的是wrapper()函数。

    5. 有参数装饰器

    有参数装饰器 = 高阶函数 + 函数嵌套 + 闭包

    如果装饰器本山需要传入参数,就需要再编写一个 decorator的高阶函数。比如给 calc函数添加一个日志功能,能够打印日志,其基本框架如下:

    def log(text):
        def timmer(func):
            def wrapper(*args, **kwargs):
                func(*args, **kwargs)
            return wrapper
        return timmer
    
    @log('文本内容')
    def calc():
        pass
    

    示例:

    import timmer
    
    def log(text):			# text: '文本内容'
        def timmer(func):				# func: calc
            """装饰器函数"""
            def wrapper(*args, **kwargs):	# args:<class 'tuple'>:('rose', 18, 'female')
                start_time = time.time()		# kwargs={}
                res = func(*args, **kwargs)		# res: 'calc 返回值'
                stop_time = time.time()
                print('函数 %s 运行时间:%s' % (func.__name__, stop_time - start_time))
                return res     # func: calc (<function calc at 0x00000000052D6D90>) 内存地址
            return wrapper
        return timmer
    
    @log('文本内容')	 # 相当于 calc = log('自定义文本')(calc)   ——> timmer(calc)  ——> calc=wrapper
    def calc(name, age, gender):
        """被修饰函数"""
        time.sleep(2)
        print('calc 运行完毕~')
        print('名字:%s,年龄:%d,性别:%s' % (name, age, gender))
        return 'calc 返回值'
    
    s = calc('rose', 18, 'female')		# 相当于执行 s = wrapper('rose', 18, 'female')
    print(s)    
    

    与两层嵌套效果的decorator相比,三层效果是这样的:

    calc = log('自定义文本')(calc)		# 即 calc = wrapper
    

    首先执行 log('自定义文本'),返回timmer函数,再调用返回参数(timmer(calc)),参数是 calc,返回值是wrapper函数,最后再调用wrapper()

    6. 对象属性

    函数也是对象,也有__name__属性(返回函数名)。但经过装饰的calc函数,它的__name__

    从原来的calc变成了wrapper

    >>> calc.__name__
    'wrapper'
    

    有些需要依赖函数签名的代码因为__name__改变,而出现某些错误,所以需要将原calc__name__属性复制到wrapper()函数中。

    import functools
    ....
    @functools.wraps(func)
    def wrapper(*args, **kwargs)
    ...
    

    7. 实例

    实例 1

    请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:

    import time
    import functools
    
    
    def metric(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            res = fn(*args, **kwargs)
            stop_time =time.time()
            print('%s executed in %s ms' % (fn.__name__, stop_time - start_time))
            return res
        return wrapper
    
    
    @metric         # fast=metric(calc)
    def fast(x, y):
        time.sleep(0.0012)
        return x + y
    
    
    @metric
    def slow(x, y, z):
        time.sleep(0.1234)
        return x * y * z
    
    f = fast(11, 22)
    s = slow(11, 22, 33)
    print(f, s)
    
    fast executed in 0.0019998550415039062 ms
    slow executed in 0.1240072250366211 ms
    33 7986
    

    实例 2

    实现一个购物网站基本功能,其功能如下:

    • 提示用户输入用户名和密码
    • 用户个人界面和购物车不需要登录(保持会话)
    import time
    import functools
    
    # 模拟存储用户名、密码数据库
    user_list=[
        {'name':'alex','passwd':'123'},
        {'name':'linhaifeng','passwd':'123'},
        {'name':'wupeiqi','passwd':'123'},
        {'name':'yuanhao','passwd':'123'},
    ]
    
    current_dic = {'username': None, 'login': False}   # 用于保存登录记录,None,False 为没有登录
    
    def auth_func(func):
        """装饰器函数"""
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 仅供 home、shopping 调用(因为不需要再输入用户名和密码)
            if current_dic['username'] and current_dic['login']:
                res = func(*args, **kwargs)
                return res
            
            username = input('用户名:').strip()
            passwd = input('密码:').strip()
            # 遍历循环用户名、密码数据库,比对用户输入的用户名和密码
            for user_dic in user_list:
                if username == user_dic['name'] and passwd == user_dic['passwd']:
                    # 用户保持会话,即保持用户登录状态,赋值
                    current_dic['username'] = username
                    current_dic['login'] = True
                    res = func(*args, **kwargs)
                    return res
            else:
                print('用户名或密码错误')
        return wrapper
    
    @auth_func
    def index():
        """主页(需要登录)"""
        print('欢迎来到xxx主页!')
        
    @auth_func    
    def home(name):
        """用户登录成功后的界面"""
        print('欢迎回家 %s' % name)
    
    @auth_func
    def shopping_car(name):
        """购物车"""
        print('%s购物车里有:[%s,%s,%s]' % (name, '游艇', '车子', '飞机'))
        
    print('before-->',current_dic) 
    index()
    print('after-->',current_dic)
    home('rose')
    shopping_car('rose')
    
    before--> {'username': None, 'login': False}
    用户名:alex
    密码:123
    欢迎来到京东主页!
    after--> {'username': 'alex', 'login': True}
    欢迎回家 rose
    rose购物车里有:[游艇,车子,飞机]
    

    带参数装饰器(需要认证类型):

    import time
    import functools
    
    user_list=[
        {'name':'alex','passwd':'123'},
        {'name':'linhaifeng','passwd':'123'},
        {'name':'wupeiqi','passwd':'123'},
        {'name':'yuanhao','passwd':'123'},
    ]
    current_dic={'username':None,'login':False}
    
    def auth(auth_type='filedb'):
        def auth_func(func):
            @funtools.wraps(func)
            def wrapper(*args,**kwargs):
                print('认证类型是',auth_type)
                if auth_type == 'filedb':
                    if current_dic['username'] and current_dic['login']:
                        res = func(*args, **kwargs)
                        return res
                    
                    username=input('用户名:').strip()
                    passwd=input('密码:').strip()
                    for user_dic in user_list:
                        if username == user_dic['name'] and passwd == user_dic['passwd']:
                            current_dic['username']=username
                            current_dic['login']=True
                            res = func(*args, **kwargs)
                            return res
                    else:
                        print('用户名或者密码错误')
                elif auth_type == 'ldap':
                    print('ldap 认证类型')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('不知道什么认证方式')
                    res = func(*args, **kwargs)
                    return res
    
            return wrapper
        return auth_func
    
    # 相当于 auth_func = auth(auth_type='filedb')(auth_func)
    @auth(auth_type='filedb') 
    def index():
        print('欢迎来到xxx主页')
    
    @auth(auth_type='ldap')
    def home(name):
        print('欢迎回家%s' %name)
    #
    @auth(auth_type='sssssss')
    def shopping_car(name):
        print('%s的购物车里有[%s,%s,%s]' %(name,'奶茶','妹妹','娃娃'))
    
    # print('before-->',current_dic)
    # index()
    # print('after--->',current_dic)
    # home('产品经理')
    shopping_car('产品经理')
    
  • 相关阅读:
    Java中NIO和IO区别和适用场景
    JDK和CGLIB动态代理原理
    java中的Serializable接口的作用
    redis采用序列化方案存对象
    在时间复杂度为O(n)且空间复杂度为O(1)的情况下翻转链表
    给定一个排好序的数组,然后求出丢失的数字
    求字符串里超过字符长度一半的元素
    求你给定两字符串包含的字母数是否完全一致
    动态规划,求数组不相邻数字的最大子串值
    JWT 工具
  • 原文地址:https://www.cnblogs.com/midworld/p/10301463.html
Copyright © 2020-2023  润新知