• <Python基础>装饰器的基本原理


    1.装饰器

      所谓装饰器一般是对已经使用(上线)的函数增加功能.

      但是因为一般的大公司的严格按照开放封闭原则(对扩展是开放的,对修改是封闭的),不会让你修改原本的函数.

      装饰器就是在不改变原本的函数且不改变原本函数的调用方式上,为原本的函数增加功能的东西.

      所以需求就是:

      1.不改变原函数

      2.不改变原函数的调用方式

      3.增加函数的功能

    1.1最基本的装饰器

      假设存在这样的原函数

    1 import time
    2 def func():
    3     time.sleep(0.01)
    4     print('func is running!')

      很简单的一个函数,我们现在要为这个原函数增加一个计算函数运行时间的功能.

      我们知道计算函数运行时间的函数可以这么写:

    1 def time_func():
    2     start = time.time() #记录运行前时间
    3     func()              #调用原函数
    4     end = time.time() #记录运行结束时间
    5     print(end - start)
    6 
    7 time_func()

      运行结果:

    1 func is running!
    2 0.01015472412109375

      这样虽然可以算出函数的运行时间,但是对原函数的调用就完全变了,为了让调用不变,我们可以将装饰器函数写成这样:

     1 def time_func(f):
     2     def inner():
     3         start = time.time() #记录运行前时间
     4         f()              #调用原函数
     5         end = time.time() #记录运行结束时间
     6         print(end - start)
     7     return inner    #返回inner的地址
     8 
     9 print(func)         #func函数的地址
    10 func = time_func(func)  #将func函数的内存地址发给time_func函数
    11 print(func)     #inner函数的地址
    12 func()

      运行结果: 

    1 <function func at 0x0000023371712E18>
    2 <function time_func.<locals>.inner at 0x00000233735867B8>
    3 func is running!
    4 0.010173320770263672

      可以看到这个装饰器函数实现了通过原函数的调用方式---func()---实现了计算函数的运行时间功能的增加.实现的最基本的装饰器功能.

      分析一下实现过程:

      1.将func函数的内存地址作为参数传给time_func函数,使inner中的f()就相当于func()调用原函数

      2.time_func将inner函数的地址赋值给变量func,让第12行的func()等价于inner()

      使得表面上是:

          func()调用原函数

      实际上是:

          func()等价于inner()

          inner调用f()

          func作为参数传给time_func函数,即f()等价于func()

    1.2  有返回值的装饰器

      最开始发原函数太简单了,我们给他增加返回值:

    1 import time
    2 def func():
    3     time.sleep(0.01)
    4     print('func is running!')
    5     return 'Hello World!'

      通过分析,我们知道前面的func()其实是inner(),而inner函数本身没有返回值,我们需要在inner()中增加接受原函数的返回值并返回的功能,可以写作:

    1 def time_func(f):
    2     def inner():
    3         start = time.time() #记录运行前时间
    4         ret = f()              #ret接受原函数的返回值原函数  ----增加的行
    5         end = time.time() #记录运行结束时间
    6         print(end - start)
    7         return ret  #返回原函数的返回值  ----增加的行
    8     return inner    #返回inner的地址
    1 func = time_func(func) 
    2 print(func())

      运行结果:

    1 func is running!
    2 0.01049661636352539
    3 Hello World!

    1.3 有参数的装饰器

      有的返回值,还缺什么才比较像基本的函数?    参数

      先来简单一点的,有限个参数的,原函数写作:

    1 import time
    2 def func(a,b):
    3     time.sleep(0.01)
    4     print('func is running!',a,b)
    5     return 'Hello World!'

      有参数和返回值不是一样么,在inner函数后面加上参数不就得了,装饰器可以写做:

    1 def time_func(f):
    2     def inner(a,b): #增加参数                 --增加的行
    3         start = time.time() #记录运行前时间
    4         ret = f(a,b)              #将inner函数接到的参数传给原函数  --增加的行
    5         end = time.time() #记录运行结束时间
    6         print(end - start)
    7         return ret  #返回原函数的返回值
    8     return inner    #返回inner的地址
    1 func = time_func(func) 
    2 print(func('haha','heihei'))   #传入haha和heihei

      运行结果:

    1 func is running! haha heihei
    2 0.010972023010253906
    3 Hello World!

      成功了!

      实际中,原函数的参数可能有很多种,数量也不可能固定不变,不可能原函数的参数一变,你就去改装饰器函数,这样太麻烦

      为了解决参数的问题,我们提出王炸策略

      *args   ------动态参数,可以接受任意数量的位置参数

      **kwargs ----动态参数,可以接受任意数量的关键字参数

      这种策略在python内置函数中多有用到,比如len函数:

    1 def len(*args, **kwargs): # real signature unknown
    2     """ Return the number of items in a container. """
    3     pass

      它能接受一切数量的参数,无论是位置参数还是关键字参数,仿照这个,我们可以写出接受任意参数的装饰器函数

      原函数:

    1 import time
    2 def func(*args, **kwargs):
    3     time.sleep(0.01)
    4     print('func is running!',*args, **kwargs)
    5     return 'Hello World!'

      装饰器函数:

    1 def time_func(f):
    2     def inner(*args, **kwargs): #增加参数    ----修改部分
    3         start = time.time() #记录运行前时间
    4         ret = f(*args, **kwargs)              #将inner函数接到的参数传给原函数-----修改部分
    5         end = time.time() #记录运行结束时间
    6         print(end - start)
    7         return ret  #返回原函数的返回值
    8     return inner    #返回inner的地址
    1 # print(func)         #func函数的地址
    2 func = time_func(func)  #将func函数的内存地址发给time_func函数
    3 # print(func)     #inner函数的地址
    4 print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

      运行结果:

    1 func is running! haha heihei 1 2 3 4 5 6 3 2 1
    2 0.010983467102050781
    3 Hello World!

    1.4  装饰器的固定模式

      前面以求运行时间为例,将其进行推广就可以得到装饰器的固定模式:

    1 def wrapper(f):    #装饰器函数,f是被装饰的函数
    2     def inner(*args,**kwargs):
    3         '''在被装饰函数之前要做的事'''
    4         ret = f(*args,**kwargs)    #被装饰的函数
    5         '''在被装饰函数之后要做的事'''
    6         return ret
    7     return inner
    8 
    9 func = wrapper(func)   #感觉有点多余

      很明显发现第九行看起来有点多余,于是有了语法糖的概念

      语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。----by百度百科

      装饰器的语法糖就是在被装饰函数,也就是原函数的前面加@装饰器函数

      像这样:

    1 @wrapper
    2 def func(*args, **kwargs):
    3     time.sleep(0.01)
    4     print('func is running!',*args, **kwargs)
    5     return 'Hello World!'

      由于@wrapper 等价于func = wrapper(func)

      而@wrapper要写在原函数上面,按照python解释器从上到下的工作规律,如果装饰器函数在原函数下面,@wrapper相当于没有意义,会报错,所以装饰器函数必须在原函数之前,整体写做这样:

     1 import time
     2 
     3 def wrapper(f):    #装饰器函数,f是被装饰的函数
     4     def inner(*args,**kwargs):
     5         '''在被装饰函数之前要做的事'''
     6         ret = f(*args,**kwargs)    #被装饰的函数
     7         '''在被装饰函数之后要做的事'''
     8         return ret
     9     return inner
    10 
    11 @wrapper
    12 def func(*args, **kwargs):
    13     time.sleep(0.01)
    14     print('func is running!',*args, **kwargs)
    15     return 'Hello World!'
    16 print(func('haha','heihei',1,2,3,4,5,6,3,2,1))

      运行结果:

    1 func is running! haha heihei 1 2 3 4 5 6 3 2 1
    2 Hello World!

      

  • 相关阅读:
    解决input 输入框频繁请求问题,如果拿取最后一次接口返回的值
    记录两个小问题
    axios 如何取消请求
    给vue组件绑定原生事件
    Vue3 与 Vue2的不同之处一 简单介绍 Vue 核心最基本的功能
    js将数组对象中,以某个值相同的对象合并成一个;即把某个值相同的对象内容合并成一个
    postcss-preset-env
    webpack5 tree shaking
    深拷贝
    webpack 性能优化
  • 原文地址:https://www.cnblogs.com/shuimohei/p/9657362.html
Copyright © 2020-2023  润新知