从小例子进入装饰器

统计一个函数执行耗时

  • 原始版本

     import time
    
     # time模块有提供时间相关函数
     def do_something():
         print("do_something")
         time.sleep(0.5)  # 让程序停止0.5秒模拟其它操作耗时
    
     start = time.time()
     do_something()
     print(time.time() - start)
     #result:
     # do_something
     # 0.5000283718109131

    问题:上述代码可以完成这个功能,但之后会发现,如果我们要统计其它函数,就必须在每个函数前后加入相应代码

  • 装饰器版本1(无参数)

     import time
    
     def execute_time(func):
         def inner():
             start = time.time()
             func()
             print(time.time() - start)
    
         return inner
    
     # time模块有提供时间相关函数
     def do_something():
         print("do_something")
         time.sleep(0.5)  # 让程序停止0.5秒模拟其它操作耗时
    
     do_something = execute_time(do_something)
     do_something()
     #result:
     # do_something
     # 0.5000283718109131

    从上述代码可以看到,使用了另一个函数execute_time()给我们要统计耗时的函数进行了包装,这时,这个execute_time()函数就叫做装饰器函数,而我们要统计的那个函数也就是do_something()函数就是被装饰的函数.问题:函数执行的时候实际上是调用的execute_time()函数中的inner()函数,这种方法虽然解决了原始版本的问题,但是当我们要统计的函数拥有返回值的时候,这时候我们获取不到返回值.

  • 装饰器版本2(有固定参数)

    import time
    
    def execute_time(func):
        def inner(do):
            start = time.time()
            result = func(do)
            print(time.time() - start)
            return result
        return inner
    
    # time模块有提供时间相关函数
    def do_something(do):
        print("do_something", do)
        time.sleep(0.5)  # 让程序停止0.5秒模拟其它操作耗时
        return 'do_something over'
    
    do_something = execute_time(do_something)
    print(do_something('say hello'))
    # result:
    # do_something say hello
    # 0.5000283718109131
    # do_something over

    为了解决装饰器版本1的问题,我在inner()函数里面加了个返回值.问题:当被装饰函数的参数个数与inner()参数个数不同时,这个装饰器就不适用了

  • 装饰器版本3(动态参数)

     import time
    
     def execute_time(func):
         def inner(*args, **kwargs):
             start = time.time()
             result = func(*args, **kwargs)
             print(time.time() - start)
             return result
    
         return inner
    
     # time模块有提供时间相关函数
     def do_something(do1,do2):
         print("do_something", do1,do2)
         time.sleep(0.5)  # 让程序停止0.5秒模拟其它操作耗时
         return 'do_something over'
    
     do_something = execute_time(do_something)
     print(do_something('say hello1','say hello2'))
     # result:
     # do_something say hello1 say hello2
     # 0.5000283718109131
     # do_something over

    在第七天内容中有个知识点是动态参数,刚好可以解决这个问题

  • 终极版本(语法糖@)

     import time
    
     def execute_time(func):
         def inner(*args, **kwargs):
             start = time.time()
             result = func(*args, **kwargs)
             print(time.time() - start)
             return result
    
         return inner
    
     @execute_time
     def do_something(do1,do2):
         print("do_something", do1,do2)
         time.sleep(0.5)  # 让程序停止0.5秒模拟其它操作耗时
         return 'do_something over'
    
     # do_something = execute_time(do_something)
     print(do_something('say hello1','say hello2'))
     # result:
     # do_something say hello1 say hello2
     # 0.5000283718109131
     # do_something over

    对于装饰器,python内部给我们提供了语法糖支持.在需要被装饰的函数名上部使用[@装饰器函数名称]即可,简化上述代码18行

装饰器进阶

获取被包装函数原生属性

  • 例1:常规函数取函数名

     def func():
         print('执行中')
         print(func.__name__)
    
     func()
     # result:
     #   执行中
     #   func

    常规函数可以通过函数的__name__属性可拿到当前函数名称

  • 例2:被装饰函数取函数名

     def wrapper(func):
         def inner():
             print('执行前')
             result = func()
             print('执行后')
             return result
         return inner;
     @wrapper
     def func():
         print('执行中')
         print(func.__name__)
    
     func()
     # result:
     #   执行前
     #   执行中
     #   inner
     #   执行后

    问题:通过执行结果会发现,结果中想获取的函数名是func,而实际结果是inner.原因是@wrapper进行包装相当于执行一个操作:func=wrapper(func)=inner

  • 解决

    使用functools模块

     from functools import wraps
    
     def wrapper(func):
         @wraps(func)
         def inner():
             print('执行前')
             result = func()
             print('执行后')
             return result
    
         return inner;
    
     @wrapper
     def func():
         print('执行中')
         print(func.__name__)
    
     func()
     # result:
     #   执行前
     #   执行中
     #   func
     #   执行后

    导入functools模块后通过第4行操作,会发现执行的函数即使被包装但还是能获取到它本身的属性

带参数的装饰器

  • 例1

     def wrapper(func):
         def inner():
             print('执行前')
             result = func()
             print('执行后')
             return result
    
         return inner;
    
     @wrapper
     def func_1():
         print('执行中')
    
     @wrapper
     def func_2():
         print('执行中')
    
     @wrapper
     def func_3():
         print('执行中')
     ...
     @wrapper
     def func_n():
         print('执行中')

    问题:通过上述代码会发现,有很多函数都用了同一个装饰器,如果有一天要取消这些函数上的装饰,就必须对每一个函数进行修改

  • 解决

    定义一个全局变量flag,并给原本装饰器外部再加一层函数用来接收参数,inner()函数内部通过外部参数flag判断被装饰函数的执行与否,修改flag即可控制装饰器的执行结果,如下:

     from functools import wraps
     flag = True
    
     def wrapper_out(flag):
         def wrapper(func):
             @wraps(func)
             def inner():
                 if (flag):
                     print('{}执行前'.format(func.__name__))
                     result = func()
                     print('{}执行后'.format(func.__name__))
                 else:
                     result = func()
                 return result
    
             return inner
    
         return wrapper
    
     @wrapper_out(flag)
     def func_1():
         print('{}执行中'.format(func_1.__name__))
    
     @wrapper_out(flag)
     def func_2():
         print('{}执行中'.format(func_2.__name__))
    
     @wrapper_out(flag)
     def func_3():
         print('{}执行中'.format(func_3.__name__))
    
     ...
    
     @wrapper_out(flag)
     def func_n():
         print('{}执行中'.format(func_n.__name__))
    
     func_1()
     func_2()
     func_3()
     func_n()
    
     #result:
         # func_1执行前
         # func_1执行中
         # func_1执行后
         # func_2执行前
         # func_2执行中
         # func_2执行后
         # func_3执行前
         # func_3执行中
         # func_3执行后
         # func_n执行前
         # func_n执行中
         # func_n执行后
    from functools import wraps
    flag = False
    
    def wrapper_out(flag):
        def wrapper(func):
            @wraps(func)
            def inner():
                if (flag):
                    print('{}执行前'.format(func.__name__))
                    result = func()
                    print('{}执行后'.format(func.__name__))
                else:
                    result = func()
                return result
    
            return inner
    
        return wrapper
    
    @wrapper_out(flag)
    def func_1():
        print('{}执行中'.format(func_1.__name__))
    
    @wrapper_out(flag)
    def func_2():
        print('{}执行中'.format(func_2.__name__))
    
    @wrapper_out(flag)
    def func_3():
        print('{}执行中'.format(func_3.__name__))
    
    ...
    
    @wrapper_out(flag)
    def func_n():
        print('{}执行中'.format(func_n.__name__))
    
    func_1()
    func_2()
    func_3()
    func_n()
    
    #result:
        # func_1执行中
        # func_2执行中
        # func_3执行中
        # func_n执行中

多个装饰器装饰同一个函数

  • 代码

     def wrapper1(func):
         def inner():
             print('wrapper1 ,before func')
             func()
             print('wrapper1 ,after func')
    
         return inner
    
     def wrapper2(func):
         def inner():
             print('wrapper2 ,before func')
             func()
             print('wrapper2 ,after func')
    
         return inner
    
     @wrapper1
     @wrapper2
     def f():
         print('in f')
    
     f()
     # result:
     # wrapper1 ,before func
     # wrapper2 ,before func
     # in f
     # wrapper2 ,after func
     # wrapper1 ,after func
  • 图解

    从上图可以看到,从1-9步是装饰器的装载过程,10-18步是执行过程.结论:多个装饰器装饰同一个函数时,装载顺序是从下到上,但执行顺序却是从上到下,可以理解为创建了一个装饰器栈(先进后出)

python基础(8)-装饰器函数&进阶的更多相关文章

  1. 十. Python基础(10)--装饰器

    十. Python基础(10)--装饰器 1 ● 装饰器 A decorator is a function that take a function as an argument and retur ...

  2. python基础之 装饰器,内置函数

    1.闭包回顾 在学习装饰器之前,可以先复习一下什么是闭包? 在嵌套函数内部的函数可以使用外部变量(非全局变量)叫做闭包! def wrapper(): money =10 def inner(num) ...

  3. Day11 Python基础之装饰器(高级函数)(九)

    在python中,装饰器.生成器和迭代器是特别重要的高级函数   https://www.cnblogs.com/yuanchenqi/articles/5830025.html 装饰器 1.如果说装 ...

  4. python基础--定义装饰器(内置装饰器)

    装饰器的定义: 装饰器本质上就是一个python函数,它可以让其它函数在不需要做任何代码改动的前提下增加额外的功能,装饰器的返回值也是一个函数对象.它经常用于有切面需求的场景中,比如-- >插入 ...

  5. [python基础]关于装饰器

    在面试的时候,被问到装饰器,在用的最多的时候就@classmethod ,@staticmethod,开口胡乱回答想这和C#的static public 关键字是不是一样的,等面试回来一看,哇,原来是 ...

  6. 1.16 Python基础知识 - 装饰器初识

    Python中的装饰器就是函数,作用就是包装其他函数,为他们起到修饰作用.在不修改源代码的情况下,为这些函数额外添加一些功能,像日志记录,性能测试等.一个函数可以使用多个装饰器,产生的结果与装饰器的位 ...

  7. python基础之装饰器(实例)

    1.必备 #### 第一波 #### def foo(): print 'foo' foo #表示是函数 foo() #表示执行foo函数 #### 第二波 #### def foo(): print ...

  8. 【Python基础】装饰器的解释和用法

    装饰器的用法比较简单,但是理解装饰器的原理还是比较复杂的,考虑到接下来的爬虫框架中很多用到装饰器的地方,我们先来讲解一下. 函数 我们定义了一个函数,没有什么具体操作,只是返回一个固定值 请注意一下缩 ...

  9. python之路——装饰器函数

    阅读目录 楔子 装饰器的形成过程 开放封闭原则 谈装饰器主要功能和装饰器固定结构 带参数的装饰器 多个装饰器装饰一个函数 返回顶部 楔子 作为一个会写函数的python开发,我们从今天开始要去公司上班 ...

随机推荐

  1. 华为S12700 NQA配置

    首先,nqa功能是需要授权的. 以下以ICMP配置方式示例: 在配置ICMP测试之前,需要NQA客户端与被测试设备间路由可达.(ICMP测试提供类似于普通ping命令的功能,但输出信息更为丰富.) 请 ...

  2. create-react-app入门教程

    Create React App是FaceBook的React团队官方出的一个构建React单页面应用的脚手架工具.它本身集成了Webpack,并配置了一系列内置的loader和默认的npm的脚本,可 ...

  3. Python2.x与3.x版本区别

    Python2.x与3.x版本区别 1.print 函数 print语句没有了,取而代之的是print()函数. Python 2.6与Python 2.7部分地支持这种形式的print语法.在Pyt ...

  4. selenium.common.exceptions.UnexpectedAlertPresentException: Alert Text: None;Message: unexpected alert open: {Alert text : 您点击的频率过快!请稍后再试}

    报错 Traceback (most recent call last): File "C:/myFiles/code/cnki/cnki_1/core/knavi.py", li ...

  5. 内建模块collections的使用

    # -*-coding:utf-8 -*- from collections import namedtuple Point=namedtuple('Point',['x','y']) p=Point ...

  6. mysql安装过程mysql本地环境变量的配置

    配置环境变量 前面步骤完成后安装好MySQL,为MySQL配置环境变量.MySQL默认安装在C:\Program Files下. 1)新建MYSQL_HOME变量,并配置:C:\Program Fil ...

  7. 误删除libc.so.6的解决

    最近安装一个软件需要glibc-2.17. 使用ldd --version 发现系统的glibc版本为 glibc-2.12,当时没有想到更好的方法,就尝试将系统的glibc版本修改为glibc-2. ...

  8. 【微信开发】cURL error 60: SSL certificate problem: unable to get local issuer certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)

    在做微信开发时候,请求为你接口报错: 解决方案: 1 下载cacert https://curl.haxx.se/ca/cacert.pem 2 修改 php.ini , 并重启 curl.cainf ...

  9. 盘点 React 16.0 ~ 16.5 主要更新及其应用

    目录 0. 生命周期函数的更新 1. 全新的 Content API 2. React Strict Mode 3. Portal 4. Refs 5. Fragment 6. 其他 7. 总结 生命 ...

  10. Visual Studio 2015编译wxWidgets

    宫指导说,换帅如换刀 程序员的编译器一换,基本套路必须都重练几次 使用wxWidgets并不难,但不能使用现有的库和工程配置文件,细节就必须理清楚 获取wxWidgets 官方的下载页面,下7z或zi ...