Python装饰器粗解学习

此次学习资料详细来自http://blog.csdn.net/u013471155

本次是粗学,仍有诸多疑问,暂且记录一二,如有不足和建议,希望可以达者指点。

三个关键点理解:

      1、关于函数“变量”(或“变量”函数)的理解

    2、关于高阶函数的理解

    3、关于嵌套函数的理解

1、装饰器

装饰器实际上就是为了给某程序增添功能,但该程序已经上线或已经被使用,那么就不能大批量的修改源代码,这样是不科学的也是不现实的,因为就产生了装饰器,使得其满足:

  1.不能修改被装饰的函数的源代码

  2.不能修改被装饰的函数的调用方式

  3.满足1、2的情况下给程序增添功能

 

装饰器的原则组成:

<函数+实参高阶函数+返回值高阶函数+嵌套函数+语法糖 = 装饰器>

2、需求的实现

假设有代码:

 

import time
def test():
      time.sleep(2)
      print("test is running")
test()    

 

很显然,这段代码运行的结果一定是:等待约2秒后,输出

test is running

  • 那么要求在满足三原则的基础上,给程序添加统计运行时间(2 second)功能

2.1高阶函数

那么对于高阶函数的形式可以有两种:

  1. 把一个函数名当作实参传给另外一个函数(“实参高阶函数”)
  2. 返回值中包含函数名(“返回值高阶函数”)

那么这里面所说的函数名,实际上就是函数的地址,也可以认为是函数的一个标签而已,并不是调用,是个名词。如果可以把函数名当做实参,那么也就是说可以把函数传递到另一个函数,然后在另一个函数里面做一些操作,根据这些分析来看,这岂不是满足了装饰器三原则中的第一条,即不修改源代码而增加功能。那我们看来一下具体的做法:

 1 import time
 2 
 3 def test():
 4       time.sleep(2)
 5       print("test is running")
 6 
 7 def deco(func):
 8       start = time.time()
 9       func() #2
10       stop = time.time()
11       print(stop - start)
12 
13 deco(test) #1

在#1处,把test当作实参传递给形参func,即func=test。这里传递的是地址,也就是此时func也指向了之前的test所定义的那个函数体,可以说在deco()内部,func就是test。在#2处,把函数后面加上括号,也就是对函数的调用(执行它)。因此,结果如下:

test is running!
the run time is 3.0009405612945557

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

如下:

 1 import time
 2 
 3 def test():
 4       time,sleep(2)
 5       print("test is running!")
 6 
 7 
 8 def deco(func):
 9 
10       print(func)
11       return func
12 
13 t = deco(test) #3
14 #t() #4
15 
16 test()

我们看这段代码,在#3处,将test传入deco(),在deco()里面操作之后,最后返回了func,并赋值给t。因此这里test => func => t,都是一样的函数体。最后在#4处保留了原来的函数调用方式。
看到这里显然会有些困惑,我们的需求不是要计算函数的运行时间么,怎么改成输出函数地址了。是因为,单独采用第二张高阶函数(返回值中包含函数名)的方式,并且保留原函数调用方式,是无法计时的。如果在deco()里计时,显然会执行一次,而外面已经调用了test(),会重复执行。这里只是为了说明第二种高阶函数的思想,下面才真的进入重头戏。

2.2嵌套函数

嵌套函数指的是在函数内部定义一个函数,而不是调用。另外还有一个题外话,函数只能调用和它同级别以及上级的变量或函数。也就是说:里面的能调用和它缩进一样的和他外部的,而内部的是无法调用的。

统计运行时间,满足三原则如下:

 1 import time
 2 
 3 def timer(func): #5
 4       
 5       def deco():
 6            
 7             start = time.time()
 8             func()
 9             stop = time.time()
10 
11        return deco
12 
13 test = timer(test) #6
14 
15 def test():
16      time.sleep(2)
17      print("test is running")
18 
19 test() #7

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

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

这段代码在本质上是修改了调用函数,但在表面上并未修改调用方式,而且实现了功能。

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

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

这样做的原因是:

我们要保留test(),还要统计时间,而test()只能调用一次(调用两次运行结果会改变,不满足),再根据函数即“变量”,那么就可以通过函数的方式来回闭包。于是乎,就想到了,把test传递到某个函数,而这个函数内恰巧内嵌了一个内函数,再根据内嵌函数的作用域(可以访问同级及以上,内嵌函数可以访问外部参数),把test包在这个内函数当中,一起返回,最后调用这个返回的函数。而test传递进入之后,再被包裹出来,显然test函数没有弄丢(在包裹里),那么外面剩下的这个test标签正好可以替代这个包裹(内含test())。

3、真正的装饰器

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

test = timer(test)

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

@timer

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

以上为无参形式

4、装饰器有参函数

 1 import time
 2 
 3 def timer(func):
 4         
 5         def deco(*args, **kwargs):
 6                 start = time.time()
 7                 res = func(*args, **kwargs)
 8                 stop = time.time()
 9                 print(stop - start)
10                 return res 
11         return deco
12 
13 @timer
14 def test(parameter): #8
15 
16         time.sleep(2)
17         print("test is running!")
18         return "Returned value"
19 
20 test()
21     
22             
23                 

5、带参数的装饰器

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

@decorator(parameter = value)

比如有两个函数:

 1 def task1():
 2         time.sleep(2)
 3         print("in the task1")
 4 
 5 def task2():
 6         time.sleep(2)
 7         print("in the task2")
 8 
 9 
10 task1()
11 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)也恰巧带了括号,那么就会执行这个函数, 那么就是相当于:

timer = timer(parameter)
task1 = timer(task1)

后面的运行就和一般的装饰器一样了:

 1 import time
 2 
 3 def timer(parameter):
 4         
 5         def outer_wrapper(func):
 6 
 7                 def wrapper(*args, **kwargs):
 8 
 9                         if parameter == 'task1':
10                             start = start.time()
11                             func(*args, **kwargs)
12                             stop = time.time()
13                             print("the task1 run time is :", stop - start)
14 
15                         elif parameter == 'task1':
16                               start = start.time()
17                               func(*args, **kwargs)
18                               stop = time.time()
19                               print("the task1 run time is :", stop - start)
20     
21                 return wrapper
22 
23         return outer_wrapper
24 
25 @timer(parameter = 'task1')
26 def task1():
27     time.sleep(2)
28     print("in the task1")
29 
30 @timer(parameter = 'task2')
31 def task2():
32     time.sleep(2)
33     print("in the task2")
34 
35 task1()
36 task2()

 

 

 

  

 

posted @ 2018-09-18 17:48  昨日不可追  阅读(161)  评论(0编辑  收藏  举报