• python学习之【第十四篇】:Python中的装饰器


    1.什么是装饰器?

    器即函数

    装饰即修饰,意指为其他函数添加新功能

    装饰器定义:本质就是函数,功能是为其他函数添加新功能

    2.遵循的原则

    装饰器必须遵循以下两个原则:

    • 不修改被装饰函数的源代码(开放封闭原则)
    • 为被装饰函数添加新功能后,不修改被修饰函数的调用方式

    3.一步一步剖析Python中装饰器原理

    我们知道,装饰器就是给其他函数动态增加功能的函数。其本质还是函数。下面我们就依据装饰器必须遵循的两个原则,手动实现一个装饰器,以此来剖析装饰器的内部原理。

    前置知识储备:

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

    3.1需求

    假设我们需要给test函数增加一个计算该函数运行时间功能,即在函数运行完毕后输出该函数运行的时间,但又不希望修改原本函数的定义,此时,我们要定义一个能计算函数运行时间的装饰器timer

    test函数如下:

    def test():
        res = 0
        for i in range(100):
            res += i
        print(res)
    

    OK,需求有了,我们现在试着动手实现一下。

    3.2 低级版实现

    import time
    
    # 定义timer函数,用来计算test函数的运行时间
    def timer(func):
        start_time = time.time()
        func()
        end_time = time.time()
        print('函数运行时间为:%s' %(start_time-end_time))
    
    def test():
        res = 0
        for i in range(100):
            res += i
        print(res)
    
    timer(test)
    

    以上代码中,当我们想运行test函数并计算该函数的运行时间时,我们只需调用timer(test)函数即可,虽然这样算是实现了需求,但是却违反了装饰器第二条原则:为被装饰函数添加新功能后,不修改被修饰函数的调用方式。最初调用test函数只需test()即可,而现在增加了新功能后必须timer(test)这样调用才行,显然这样是不行的,代码还需要继续改进!

    3.3 进阶版实现

    试想一下,如果我们像timer(test)这样调用timer()函数时,并不让timer()函数直接执行,而是让它返回一个函数的地址,然后我们在把这个地址赋值给一个变量,然后再以变量( )的方式调用。这样做的好处就是:这个变量名我们可以自定义,假如我们把这个变量名还叫test,那么后续调用test()就等于调用了上面代码中的timer(test)。这样我们就遵循了装饰器的两大原则:既不修改原函数的代码,也没有修改原函数的调用方式。

    话不多说,来试试看:

    import time
    
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print('函数运行时间为:%s' % (start_time - end_time))
    
        return wrapper
    
    
    def test():
        res = 0
        for i in range(100):
            res += i
        print(res)
    
    
    # timer(test)返回函数地址wrapper,将地址赋值给变量test
    test = timer(test)
    # 调用test()即执行了返回的函数地址wrapper函数
    test()
    

    3.4 高阶版完美实现

    通过进阶版的实现,我们想计算哪个函数的运行时间,只需在该函数调用之前加一行函数名=timer(函数名)代码即可。

    但如果函数非常多的话,这样还是有些繁琐。其实这个问题Python早就替我们想到了,Python提供一个语法糖@有了这个语法糖,我们只需在被装饰函数定义前加@timer(函数名)即可,@timer(函数名)就等于函数名=timer(函数名)

    import time
    
    def timer(func):
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print('函数运行时间为:%s' % (start_time - end_time))
    
        return wrapper
    
    
    @timer     # @timer等同于test = timer(test)
    def test():
        res = 0
        for i in range(100):
            res += i
        print(res)
    
    
    test()
    

    3.5 总结

    通过以上三步实现,我们即可总结出装饰器原理:

    @timer放到被装饰函数test的定义之前,就相当于执行了test = timer(test),由于timer()是一个高阶函数,它返回一个函数地址,所以原来的test函数仍然存在,只是现在同名的test变量指向了新的函数,于是调用test()将执行在timer()函数中返回的wrapper()函数,在wrapper()函数内,首先执行原始函数,再计算函数运行时间。

    4. 装饰器执行时机

    # 定义装饰器
    def timer(func):
        print('----正在装饰----')
        def wrapper():
            start_time = time.time()
            func()
            end_time = time.time()
            print('函数运行时间为:%s' % (start_time - end_time))
    
        return wrapper
    
    
    @timer     # @timer等同于test = timer(test)
    def test():
        res = 0
        for i in range(100):
            res += i
        print(res)
    

    执行以上代码,我们可以发现,当没有调用test()函数时,装饰器也会执行,也就是说,只要python解释器执行到了@timer这句代码,那么就会自动进行装饰,而不是等到调用的时候才装饰,在函数调用之前就已经对函数装饰完毕了.

    5. 多个装饰器执行顺序

    如果一个函数被多个装饰器装饰时,那么装饰顺序是怎样的呢?看以下代码:

    # 定义装饰器1:加粗
    def makeBold(func):
        print('-----准备装饰1-----')
        def wrapper():
            print('-----装饰中1-----')
            return '<b>' + func() + '</b>'
        return wrapper
    
    # 定义装饰器2:斜体
    def makeItalic(func):
        print('-----准备装饰2-----')
        def wrapper():
            print('-----装饰中2-----')
            return '<i>' + func() + '</i>'
        return wrapper
    
    @makeBold
    @makeItalic
    def test():
        return 'Hello World'
    
    print(test())
    # 输出
    # -----准备装饰2-----
    # -----准备装饰1-----
    # -----装饰中1-----
    # -----装饰中2-----
    # <b><i>Hello World</i></b>
    

    运行以上代码,从输出结果上来看,当一个函数被多个装饰器装饰时,装饰的时候是从里往外装,装饰的结果也是一层一层往外套,但是装饰器执行时却是从外往里执行。

    6.装饰器基本框架

    # 装饰器基本框架
    def decorator(func):
        def wrapper():
            func()
        return wrapper
    

    7.被装饰函数有参数

    # 被装饰函数有参数
    def decorator(func):
        def wrapper(a,b):
            print('---装饰器---')
            func(a,b)
        return wrapper
    
    @decorator
    def test(a,b):
        print('----test--%s+%s' %(a,b))
    
    test(1,2)
    

    我们知道,当调用test(1,2)函数时,其实是调用了wrapper函数,所以要给wrapper函数传入相应的参数,而wrapper函数内部的func函数才是真正调用test函数,所以也要给func传入相应的参数。

    8. 被装饰函数有return

    如果被装饰的函数有返回值,那么我们需要在装饰器中将返回值接收一下。

    # 被装饰函数有参数
    def decorator(func):
        def wrapper(a,b):
            print('---装饰器---')
            ret = func(a,b)
            return ret
        return wrapper
    
    @decorator
    def test(a,b):
        return a + b
    
    test(1,2)
    

    通常情况下,为了装饰器的通用性,无论被装饰函数是否有返回值,都会在装饰器中加上return

    9. 通用装饰器

    无论被装饰函数是否有return,有多少个参数,均可以适用的通用装饰器。

    # 通用装饰器
    
    def decorator(func):
        def wrapper(*args, **kwargs):
            ret = func(*args, **kwargs)
            return ret
    
        return wrapper
    
    
    # 被装饰函数无参数,无返回值
    @decorator
    def test1():
        print('----test--')
    
    
    # 被装饰函数有参数,无返回值
    @decorator
    def test2(a, b):
        print('----test--%s+%s' % (a, b))
    
    
    # 被装饰函数无参数,有返回值
    @decorator
    def test3():
        return 'test'
    
    
    # 被装饰函数有参数,有返回值
    @decorator
    def test4(a, b):
        return a + b
    
    
    test1()             # 输出----test--
    test2(1, 2)         # 输出----test--1+2
    print(test3())      # 输出test
    print(test4(1, 2))  # 输出3
    

    10. 装饰器本身带有参数

    如果装饰器本身需要传入参数,那就需要编写一个返回装饰器的高阶函数。

    # 装饰器本身带有参数
    
    def decorator(text):
        def wrapper(func):
            def inner(*args, **kwargs):
                print('--%s是装饰器的参数---' %text)
                ret = func(*args, **kwargs)
                return ret
            return inner
        return wrapper
    
    
    @decorator('execute')
    def test(a, b):
        return a + b
    
    
    print(test(1, 2))
    # 输出
    # --execute是装饰器的参数---
    # 3
    
    

    和两层嵌套的装饰器相比,3层嵌套的效果是这样的:

    @decorator放到test()函数的定义处,相当于执行了语句:

    test = decorator('execute')(test)(),首先执行decorator('execute'),返回的是wrapper函数,再调用返回的函数,参数是test函数,返回值最终是inner函数。最后再执行inner函数。

    11.装饰器应用

    我们可以编写一个验证用户是否登录的装饰器,在用户调用函数之前,先验证用户是否登录,如果登录,则可正常访问,如果未登录,则提示用户先登录。

    # 装饰器应用实例:验证是否登录
    user_list = [
        {'name': 'haha', 'password': '123'}
    ]
    
    current_user = {'name': None, 'login': False}
    
    
    def auth_dec(func):
        def wrapper(*args, **kwargs):
            if current_user['name'] and current_user['login']:
                ret = func(*args, **kwargs)
                return ret
            else:
                print('您还未登录,请您先登录,再访问页面!')
                username = input('请输入用户名:')
                pw = input('请输入密码:')
    
                for user_dic in user_list:
                    if username == user_dic['name'] and pw == user_dic['password']:
                        current_user['name'] = username
                        current_user['login'] = True
                        ret = func(*args, **kwargs)
                        return ret
                        break
                print('用户名或密码错误')
    
        return wrapper
    
    
    @auth_dec
    def index():
        print('欢迎来到主界面')
    
    
    def home():
        print('欢迎来到家园')
    
    
    index()
    home()
    

    (完)

  • 相关阅读:
    SDN课程阅读作业(2)
    2019 SDN上机第5次作业
    第05组 Alpha事后诸葛亮
    第05组 Alpha冲刺(4/4)
    2020-4-5助教一周小结
    2020-3-29助教一周小结
    2020-3-22助教一周小结
    2020-03-15助教一周小结
    2020-03-08助教一周小结
    第一次个人编程作业(个人实现)
  • 原文地址:https://www.cnblogs.com/wangjiachen666/p/9764873.html
Copyright © 2020-2023  润新知