zoukankan      html  css  js  c++  java
  • 装饰器详解

    装饰器(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('产品经理')
    
  • 相关阅读:
    剑指 Offer——13. 调整数组顺序使奇数位于偶数前面
    剑指 Offer——3. 从尾到头打印链表
    剑指 Offer——2. 替换空格
    剑指 Offer——1. 二维数组中的查找
    LeetCode 905. Sort Array By Parity 按奇偶校验排列数组
    LeetCode 448. Find All Numbers Disappeared in an Array找到所有数组中消失的元素
    SSH 代码笔记
    anaconda3安装caffe
    opencv多版本安装
    人脸文章与数据库
  • 原文地址:https://www.cnblogs.com/midworld/p/10301463.html
Copyright © 2011-2022 走看看