8、Python之装饰器

一、装饰器

     首先我们先从装饰器的定义来理解什么是装饰器,装饰器就是为一个函数添加额外的功能,但必须满足两个条件1、不改变函数的源代码 2、不改变函数的调用方式。举个栗子,你是一个帅气的小伙子,然后我给你带个耳钉,穿上一件漂亮的旗袍,再带个假发。这时候的你看上去就像个女人,我没有本质上改变你(不改变函数的源代码)(如果你去泰国的话,本质上就改变了),这时候有人叫你的名字你也会答应(不改变函数的调用方式),这样的一个过程就称为装饰,具有这种装饰其它函数的函数就叫做装饰器。

    通过上面的定义,我们总结装饰器的特性如下:

  • 装饰器本质上就是一个函数
  • 装饰器不改变装饰函数的源代码
  • 装饰器不改变装饰函数的调用方式

二、忘记是为了更好的相遇

    让我们先忘记什么是装饰器吧,我们先定义一个函数:

1 def fun_01(x):           #定义函数fun_01
2     print("print %s in fun_01")
3 
4 fun_01(10)  #调用函数fun_01

 

上面的程序分为两部分,一部分为fun_01函数的定义,另一部分为fun_01函数的调用。下面我们定义一个这样的函数:

1 import  time
2 def deco(f):
3     print("当前系统时间%s" % time.time())
4     return f

这是一个高阶函数,形参需要传递一个函数“变量”(函数名即变量,函数体即变量值)然后打印出系统当前时间,最后将这个“变量”返回。下面我们编写以下代码:

1 def fun_01(x):           #定义函数fun_01
2     print("print %s in fun_01"% x)
3 
4 fun_01 = deco(fun_01)  #调用函数fun_01
5 fun_01(10)

执行结果为当前系统时间Fri Jan 26 11:29:19 2018    print 10 in fun_01

我们先通过一个图来理解一下程序的运行过程:

程序一定是分2个部分:定义执行,上图展示了定义两个函数在内存中的存储形式。当执行fun_01 = deco(fun_01)时,fun_01的值作为实参传递给deco函数的形参,这边值得需要说一下的是:deco是“变量”而deco()就是执行函数

所以deco(fun_01)就是把fun_01的值传递给deco(f)中的f然后执行函数。打印出当前系统时间Fri Jan 26 11:29:19 2018,接着函数将f返回,实际上f=fun_01,所以搞了一圈fun_01里面的值压根没变。这时候我们重新回忆一下装饰器的

定义:1、不修改函数的源代码(没有修改fun_01函数的代码)2、不改变函数的调用方式(调用方式还是fun_01(10))。当然多了一行fun_01 = deco(fun_01)请先忘记它吧!这样看上去貌似deco就是一个装饰器嘛!too young too simple!

如果我想给fun_01装饰这样一个功能,就是统计执行fun_01方法花了多长时间,怎么搞?是不是傻了?没关系,且听我娓娓道来!

      我现在将deco这个方法稍微改造一下:

 1 def timer(f):
 2     def deco(x):
 3         before = time.time()
 4         f(x)
 5         after = time.time()
 6         print("执行函数%s一共用了%s的时间" % (f,after-before))
 7     return deco
 8 
 9 def fun_01(x):           #定义函数fun_01
10     print("print %s in fun_01"% x)
11 
12 fun_01 = timer(fun_01)  #调用函数fun_01
13 fun_01(10)

 

 我给deco函数外面又加了一个函数timer,timer函数是一个嵌套函数,timer函数返回一个deco的“变量”。然后我们再次运行程序,结果为

print 10 in fun_01

执行函数<function fun_01 at 0x0399C108>一共用了0.0的时间

 

 下面我只能用我强大的绘图能力来描述整个事件的发生过程了。

执行完fun_01 = timer(fun_01)语句后fun_01指向了新的代码块,然后执行fun_01(10)语句,执行fun_01指向的的那段代码,即为我看到执行结果。到此为止,一个新的术语正式诞生:装饰器。timer就是一个装饰器,同时我们先发现装饰器其实就是由:高阶+嵌套组成的特殊函数。python中对于装饰器的调用有专门的语法,及在装饰函数定义前面加上@装饰器,将上面的代码修改如下:

 1 def timer(f):
 2     def deco(x):
 3         before = time.time()
 4         f(x)
 5         after = time.time()
 6         print("执行函数%s一共用了%s的时间" % (f,after-before))
 7     return deco
 8 @timer
 9 def fun_01(x):           #定义函数fun_01
10     print("print %s in fun_01"% x)
11 
12 fun_01(10)

两段代码的区别在于使用@timer替代了fun_01 = timer(fun_01),其实@timer对于python解释器而言,就是执行的fun_01 = timer(fun_01)语句。因此,如果我们要装饰某个函数时,先写一个装饰这个函数的装饰器,然后再这个函数的定义前面加上@装饰器,其他地方无需改动,这时候被装饰的函数就具有了新的功能。

三、更好的装饰器

    有时候我们在给一类函数装饰的时候,可能会根据情况进行装饰,比如同时给连接oracle数据库的方法和连接mysql数据库的方法写一个装饰器,我们可能会首先判断这个方法是连接的oracle数据库还是mysql,然后再决定如何打印日志。

这时候我们需要写一个更加牛B的装饰器,代码如下(但愿你能理解):

 1 import  time
 2 def logger(method):
 3     def deco(f):
 4         def innerdeco(*args,**kwargs):
 5             if method == "mysql":
 6                 print("mysql-调用的是%s的方法" % f)
 7             elif method == "oracle":
 8                 print("oracle-调用的是%s的方法" % f)
 9             f(*args, **kwargs)
10         return innerdeco
11     return deco
12 
13 @logger(method="mysql")
14 def connectMysql(ip,port): #连接mysql
15     print("连接mysql数据库,ip为%s,端口为%s" % (ip,port))
16 @logger(method="oracle")
17 def connectOracle(ip,port,service): #连接service
18     print("连接oracle数据库,ip为%s,端口为%s ,服务名为%s" % (ip,port,service))
19 
20 
21 # p = connectMysql
22 # connectMysql = logger("mysql")
23 # connectMysql = connectMysql(p)
24 connectMysql("192.168.12.1",8080)
25 connectOracle("192.168.12.1",8080,"testmu")

如果这段代码你能看明白,说明你对装饰器已经了如指掌了。代码中21,22,23行的代码等价于@logger(method="mysql")。

posted @ 2018-01-26 14:20  高文祥888888  阅读(326)  评论(1编辑  收藏  举报