• Python的装饰器


     

    概要

    1. 什么是装饰器
    2. 怎么使用装饰器
    3. 使用场景
    4. 如何给装饰器传递参数

    1. 什么是装饰器

    能够对其他函数的功能进行增强。也就是函数代码功能重用。它是一个设计模式。需要注意的是:

    1. 装饰器本身是一个函数
    2. 增强被装饰函数的功能,同时被增强的函数还不能感受到自己被增强了或者说被修了
    3. 装饰器需要接受一个函数对象作为参数以对其进行包装
    4. 不会改变被调用函数的调用方式

    2. 怎么使用装饰器

    要理解装饰器你需要理解如下内容:

    1. 函数即变量  def 定义一个函数就等于把函数体赋值给了一个变量(函数名)
    2. 高阶函数 满足两个条件 把一个函数名当做形参传入另外一个函数、返回值中包含函数名
    3. 嵌套函数 函数中嵌套函数

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

    2.1 函数嵌套

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    def fun3():
        print("in the fun3")
        def fun4():
            print("in the fun4")
        fun4()
    
    
    if __name__ == '__main__':
        fun3()

    执行结果

    在fun3中定义一个fun4函数,执行fun3,同时也会执行fun4。这个比较好理解就是函数中嵌套一个函数,对于嵌套的函数(这里是fun4)你可以在你调用的函数(这里是fun3)中执行,也可以把这个fun4返回。

    2.2 高阶函数

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    def fun1():
        print("Hello fun1")
    
    
    def fun2(fun):
        print("World fun2")
        # 这里是执行的传入的函数本身
        fun()
        # 返回回去
        return fun
    
    # 执行fun2同时把fun1当做参数传递进去
    fun2(fun1)

    上面满足高阶函数的2个条件。但是如果我只是想给fun1增加功能,那么我这样执行显然成功了,可是我改变了调用方式,也就是说我必须执行fun2(fun1)才行,而我程序中很多地方写的都是fun1(),那如果我这样改起来自己麻烦同时如果别人调我的fun1(),那别人也要改。如何不改变调用方式呢?通过下面的方式调用,不过这不再是单纯的给fun1增加功能,因为最后相当于重新定义了fun1。

    # 如何不改变调用方式也增加新功能呢?就这样。这里相当于把之前的fun1重新定义了
    fun1 = fun2(fun1)

    2.3 装饰器如何使用

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    import time
    
    def timer(func):
        def wapper(*args, **kwargs):
            start_time = time.time()
            func()
            stop_time = time.time()
            print("耗时:", stop_time - start_time)
        return wapper
    
    @timer
    def run():
        print("To do job.")
        time.sleep(2)
        print("Job is done.")
    
    run()

    我这里是给run函数增加一个功能计算该函数的运行时间。上面就是装饰器的用法。增加了功能、没有改变原来的调用方式,timer函数随处可以引用。如果上面的功能通过高阶函数如何实现呢?你看下面的代码几乎完全相同,只是run()函数上面去掉了@timer,以及后面的执行语句前面有加了一个语句,正像上面所说执行完 run = timer(run) 其实就等于重新定了run()。

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    import time
    
    def timer(func):
        def wapper(*args, **kwargs):
            start_time = time.time()
            func()
            stop_time = time.time()
            print("耗时:", stop_time - start_time)
        return wapper
    
    
    def run():
        print("To do job.")
        time.sleep(2)
        print("Job is done.")
    
    run = timer(run)
    run()

    加上 @timer 之后,运行 run()就等于运行 timer函数中的wapper。因为:如果不加 @timer 你可以这么用,其实这么用的效果和加 @timer 然后调用run()是一样的。

    加上 @timer 之后,执行过程是什么样的呢?

    1. 从上到下执行,遇到 def timer(func) 函数解释器知道是定义了一个函数,因为此时没有调用所以解释器继续向下执行
    2. 遇到@timer,解释器执行 def timer(func) 并把 run() 函数传递给 def timer(func) 中的 func,然后发现定义了一个函数 def wapper,它此时也不执行,继续向下,遇到return wapper 则返回wapper函数
    3. 此时装饰完毕其实也就是对run()函数进行了重新赋值,继续向下,遇到run()这条语句,也就是我们直接调用,这时候它会去执行之前返回的wapper函数,此时run()就是wapper()函数  理解为 run = timer(run)
    4. 执行wapper函数的第一条语句 start_time = time.time()
    5. 执行wapper函数的第二条语句 func() 而这时候的func()就是之前传递进来的run()函数,此时执行run()函数的语句,直到执行完成返回
    6. 执行执行wapper函数第三条语句 stop_time = time.time()
    7. 执行执行wapper函数第四条语句  print("耗时:", stop_time - start_time)

    2.4 装饰器如何传递参数

    我们可能注意到 def wapper(*args, **kwargs) 其实这里就可以传递参数。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    
    def printName(func):
        def wapper(*args, **kwargs):
            func(**kwargs)
        return wapper
    
    @printName
    def run(name):
        print("姓名为:", name)
    
    def main():
        run(name="chen")
    
    if __name__ == '__main__':
        main()

    可能有些人对 **kwargs 有些迷惑,其实它就是一个字典形式,你传进来的参数必须是 KEY=VALUE形式,就行我在main()函数中的那样 run(name="chen"),如果还不明白我换一种写法如下图:

    2.5 类中如何使用装饰器

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    """
    
    """
    
    import sys
    import time
    
    
    def timer(tagName):
        import time
    
        def wapper(func):
            def aa(self, *args, **kwargs):
                start = time.time()
                func(self)
                end = time.time()
                consume = end - start
                if consume > 60:
                    min, sec = divmod(consume, 60)
                    print(" - %s 执行耗时:%s 分 %s 秒。" % (tagName, str(min), str(sec)))
                else:
                    print(" - %s 执行耗时:%s 秒。" % (tagName, str(consume)))
            return aa
    
        return wapper
    
    
    class AA:
        def __init__(self):
            pass
    
        @timer(tagName="runTask方法")
        def runTask(self):
            time.sleep(2)
    
    
    if __name__ == "__main__":
        try:
            aa = AA()
            aa.runTask()
        except Exception as err:
            print(err)
        finally:
            sys.exit()

    3. 实例

    我这里模拟一个场景就是网站登录,我们需要给所有页面增加一个验证功能。

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    """
    模拟网站登录,也就是给某些页面加验证功能
    """
    
    users = {"Tom": "123"}
    
    
    # 验证功能
    def auth(func):
        def wrapper(*args, **kwargs):
            username = input("Input your username: ")
            password = input("Input your password: ")
            if username.strip() in users and password.strip() == users[username]:
                func(username=username)
                # 如果你需要接收被装饰函数的返回值就要
                # res = func(username=username)
            else:
                exit("Invalid username or password.")
        return wrapper
    
    
    # 下面是模拟两个页面
    
    def indexPage():
        print("Welcome to Index.")
    
    
    # 给home页面加验证
    @auth
    def homePage(**kwargs):
        print("Welcome: ", kwargs["username"], "to Home.")
        return "You are Home."
    
    
    def main():
        indexPage()
        homePage()
    
    
    if __name__ == '__main__':
        main()

    执行结果

    现在的需求变了,我又增加了一个页面,同时这个页面需要另外一种验证方式,怎么办?最笨的方法是再写一个装饰器用于其他验证方式,有没有更好的办法呢?就是设置装饰器参数。

    #!/usr/bin/env python
    # Author: rex.cheny
    # E-mail: rex.cheny@outlook.com
    
    """
    模拟网站登录,也就是给某些页面加验证功能
    """
    
    users = {"Tom": "123"}
    
    def authG_two(auth_type="local"):
        """
        这个装饰器带了参数,所以它就不能写fun,而必须是明确的参数。而且这里和之前比又多了一层,
        因为最外层不是传递的函数进来,而是参数。这个是如何执行的呢?
    
        @authG_two(auth_type="local")
        def fun1():
            pass
    
        fun1 = authG_two(auth_type="local") 然后返回 wrapper,这时候 fun1 = wrapper(fun1),然后在进入到  authG_two() 执行 wrapper()方法
        根据auth_type返回 对应的函数,如果是local 最终则是  fun1 = localAuth()并且 localAuth()里的func函数就是之前传递进去的func函数。
        """
        def wrapper(func):
            # 本地验证方式
            def localAuth(*args, **kwargs):
                username = input("Input your username: ")
                password = input("Input your password: ")
                if username.strip() in users and password.strip() == users[username]:
                    print("Local authentication succeed.")
                    func(username=username)
                    # 如果你需要接收被装饰函数的返回值就要
                    # res = func(username=username)
                else:
                    exit("Invalid username or password.")
    
            # LDAP验证方式
            def ldapAuth(*args, **kwargs):
                username = input("Input your username: ")
                password = input("Input your password: ")
                func(username=username)
                print("LDAP authentication succeed.")
    
            if auth_type == "local":
                return localAuth
            else:
                return ldapAuth
        return wrapper
    
    
    """
    下面三个模仿三个板块的主页
    """
    def indexPage():
        print("Welcome to Index.")
    
    
    @authG_two()
    def homePage(**kwargs):
        """
        这里使用新的装饰器,它后面带括号,不设置参数表示使用默认参数设置。
        """
        print("Welcome: ", kwargs["username"], "to Home.")
        return "You are Home."
    
    
    # 新增BBS页面
    @authG_two(auth_type="ldap")
    def bbsPage(**kwargs):
        """
        这里使用新的装饰器,它后面带括号,这就说明那个装饰器需要带参数,验证方式为LDAP
        """
        print("Welcome to BBS.")
    
    
    def main():
        indexPage()
        homePage()
        bbsPage()
    
    
    if __name__ == '__main__':
        main()

    执行结果

  • 相关阅读:
    插件开发遇到的坑------final 型变量,编译过程被优化
    java.lang.NoClassDefFoundError 错误解决思路
    Android stadio bug
    android去掉button默认的点击阴影
    Andrid 打印调用堆栈
    Gradle 设置本地meaven
    Android log 里面快速搜索错误堆栈 ( 关键字)
    java doc 编写
    android 怎么判断activity 从哪里启动的
    Android Stadio调试gradle 插件 || Android Stadio 远程调试 || Anroid APT调试
  • 原文地址:https://www.cnblogs.com/rexcheny/p/9496853.html
Copyright © 2020-2023  润新知