• python函数装饰器详解


    python装饰器(fuctional decorators)简单来说就是修改其他函数的函数。

    这样的函数需要满足两个个条件:

    1、不能修改原函数的源代码

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

    需要达到的效果:增加函数的功能

    假设,我们已经定义了一个函数

    import time

    def test():

      time.sleep(2)

      print('The is test')

    现在需要为这个程序添加一个功能:显示该程序运行的时间

    一、预备知识

    理解装饰器的难点在于:

    1、理解python一切皆为对象的含义

    2、高阶函数

    3、嵌套函数

    1、一切皆对象

    这里只是简单看一下,理解函数即变量即可。后面学习深入会有更深入的理解。

     1 >>> def say(name):
     2 ...     return 'hello '+ name
     3 ...
     4 >>> print(say())
     5 >>> print(say('Michael'))
     6 hello Michael
     7 >>>
     8 # 我们可以将函数名作为变量赋值给另一个变量,换句话说变量可以指向函数
     9 >>> greet=say
    10 >>> print(greet())
    11 >>> print(greet('Bob'))
    12 hello Bob
    13 # 变量删除,对象也就不存在了
    14 >>> del say
    15 >>> say('Jack')
    16 Traceback (most recent call last):
    17   File "<stdin>", line 1, in <module>
    18 NameError: name 'say' is not defined
    19 >>>

    2、高阶函数

    高阶函数有两类:
    a.把一个函数名当作实参传给另外一个函数
    b.返回值中包含函数名

    python一切皆为对象,实际上函数名就是函数的地址,也可以认为,是函数的一个标签。这样就可以把函数名作为一个参数传给另一个函数,在另一个函数里做一些操作,就可以在不修改源代码的基础上,为函数增加一些功能。

     1 import time
     2 def test():
     3     print('This is test')
     4 
     5 def deco(func):
     6     start_t = time.time()
     7     func()
     8     end_t = time.time()
     9     print('the func %s run time is %s' % (func, end_t-start_t))
    10 
    11 deco(test)

    在第11行处,把test作为实参传给形参func,在第7行处就是对形参的调用。func传入的是函数的地址,func()就是执行这个函数,这段代码运行的结果就是:

    This is test
    the func <function test at 0x00000195C816EAE8> run time is 0.0

    那么再思考,如果不修改调用方式,就是一定要有test()这条语句,那么就用到了第二种高阶函数,即返回值中包含函数名

     1 def test():
     2     time.sleep(3)
     3     print('This is test')
     4 
     5 def deco(func):
     6     print('This is deco')
     7     return func
     8 
     9 test = deco(test)
    10 test()

    这种方式虽然可以不改变调用方式,但是无法完成计时功能,运算结果

    This is deco
    This is test

    3、嵌套函数

     1 import time
     2 def timer(func):
     3     def deco():
     4         start = time.time()
     5         func()
     6         stop = time.time()
     7         print('The func running time is %s' % (stop - start))
     8     return deco
     9 
    10 def test():
    11     time.sleep(2)
    12     print("This is test")
    13 
    14 test = timer(test)
    15 test()

    在第14行处,把test作为参数传递给了timer(),此时,在timer()内部,func = test,接下来,定义了一个deco()函数,当并未调用,只是在内存中保存了,并且标签为deco。在timer()函数的最后返回deco()的地址deco。

    然后再把deco赋值给了test,那么此时test已经不是原来的test了,也就是test原来的那些函数体的标签换掉了,换成了deco。那么在第15行处调用的实际上是deco()。执行结果

    This is test
    The func running time is 2.000332832336426

    那么通俗一点的理解就是:

    把函数看成是盒子,test是小盒子,deco是中盒子,timer是大盒子。程序中,把小盒子test传递到大盒子temer中的中盒子deco,然后再把中盒子deco拿出来,打开看看(调用)。

    二、真正的装饰器

    根据以上分析,装饰器在装饰时,需要在每个函数前面加上:

    test = timer(test)

    显然有些麻烦,Python提供了一种语法糖,即:

    @timer

    这两句是等价的,只要在函数前加上这句,就可以实现装饰作用。

    以上为无参形式。

     1 import time
     2 
     3 def decorator(func):
     4     def wapper():#若被装饰函数含参,传到wapper即可
     5         start_t = time.time()
     6         func()
     7         stop_t = time.time()
     8         print('the func running time is %s' % (stop_t-start_t))
     9     return wapper
    10 
    11 @decorator  #test1=decorator(test1) =wapper大盒子替换到中盒子
    12 def test():#表面是小盒子,实际上是中盒子
    13     time.sleep(3)
    14     print('in the test1')
    执行结果:
    in the test1 the func running time is 3.0000483989715576

    含参的装饰器

    对于一个实际问题,往往是有参数的,如果要在#8处,给被修饰函数加上参数,显然这段程序会报错的。错误原因是test()在调用的时候缺少了一个位置参数的。而我们知道test = func = deco,因此test()=func()=deco() ,那么当test(parameter)有参数时,就必须给func()和deco()也加上参数,为了使程序更加有扩展性,因此在装饰器中的deco()和func(),加如了可变参数*agrs和 **kwargs。

     1  improt time
     2 
     3  def timer(func)
     4      def deco(*args, **kwargs):  
     5          start = time.time()
     6          func(*args, **kwargs)
     7          stop = time.time()
     8          print(stop-start)
     9      return deco
    10 
    11  @timer
    12  def test(parameter): 
    13      time.sleep(2)
    14      print("test is running!")   
    15  test() 

    带返回值的装饰器

    test()返回值返回到deco()的内部,而不是test()即deco()的返回值,那么就需要再返回func()的值,因此就是:

    def timer(func)
         def deco(*args, **kwargs):  
             start = time.time()
             res = func(*args, **kwargs)#9
             stop = time.time()
             print(stop-start)
             return res#10
         return deco

    一个较为完整的装饰器

     1  improt time
     2 
     3  def timer(func)
     4      def deco(*args, **kwargs):  #含参的test()函数
     5          start = time.time()
     6          res = func(*args, **kwargs)
     7          stop = time.time()
     8          print(stop-start)
     9          return res #函数带有返回值
    10      return deco
    11 
    12  @timer
    13  def test(parameter): 
    14      time.sleep(2)
    15      print("test is running!")   
    16      return "Returned value"
    17  test() 

    更复杂的装饰器

    又增加了一个需求,一个装饰器,对不同的函数有不同的装饰。那么就需要知道对哪个函数采取哪种装饰。因此,就需要装饰器带一个参数来标记一下。例如:

    @decorator(parameter = value)

    比如两个函数

     def task1():
         time.sleep(2)
         print("in the task1")
    
     def task2():
         time.sleep(2)
         print("in the task2")
    
     task1()
     task2()

    要对这两个函数分别统计运行时间,但是要求统计之后输出:

    the task1/task2 run time is : 2.00……

    于是就要构造一个装饰器timer,并且需要告诉装饰器哪个是task1,哪个是task2,也就是要这样:

     1  @timer(parameter='task1') #
     2  def task1():
     3      time.sleep(2)
     4      print("in the task1")
     5 
     6  @timer(parameter='task2') #
     7  def task2():
     8      time.sleep(2)
     9      print("in the task2")
    10 
    11  task1()
    12  task2()

    那么方法有了,但是我们需要考虑如何把这个parameter参数传递到装饰器中,我们以往的装饰器,都是传递函数名字进去,而这次,多了一个参数,要怎么做呢? 
    于是,就想到再加一层函数来接受参数,根据嵌套函数的概念,要想执行内函数,就要先执行外函数,才能调用到内函数,那么就有:

     1 def timer(parameter): #
     2     print("in the auth :", parameter)
     3 
     4     def outer_deco(func): #
     5         print("in the outer_wrapper:", parameter)
     6 
     7         def deco(*args, **kwargs):
     8 
     9         return deco
    10 
    11     return outer_deco

    首先timer(parameter),接收参数parameter=’task1/2’,而@timer(parameter)也恰巧带了括号,那么就会执行这个函数, 那么就是相当于:

    1 timer = timer(parameter) 
    2 task1 = timer(task1)

    完整的实现

     1 import time
     2 def timer(parameter):
     3     def outer_wrapper(func):
     4         def wrapper(*args, **kwargs):
     5             if parameter == 'task1':
     6                 start = time.time()
     7                 func(*args, **kwargs)
     8                 stop = time.time()
     9                 print("the task1 run time is :", stop - start)
    10             elif parameter == 'task2':
    11                 start = time.time()
    12                 func(*args, **kwargs)
    13                 stop = time.time()
    14                 print("the task2 run time is :", stop - start)
    15 
    16         return wrapper
    17 
    18     return outer_wrapper
    19 @timer(parameter='task1')#1 timer = timer(parameter)此时timer=out_wrapper  2 task1 = timer(task1)此时相当于task1=wrapper(task1)
    20 def task1(): 

    21 time.sleep(2)

    22 print("in the task1")

    23 @timer(parameter='task2')
    24 def task2():

    25 time.sleep(2)

    执行结果

    in the task1
    the task1 run time is : 2.000471591949463
    in the task2
    the task2 run time is : 2.000399589538574
  • 相关阅读:
    oracle导入dmp数据库文件
    Merge into的使用详解-你Merge了没有【转】
    远程调试
    安卓Activity、service是否处于同一进程
    AIDL机制实现进程间的通讯实例
    安卓android:scaleType属性
    oracle索引
    Json-lib用法
    浅谈position: absolute和position:relative
    Tab Layout教程
  • 原文地址:https://www.cnblogs.com/weststar/p/11360127.html
Copyright © 2020-2023  润新知