python之循序渐进学习装饰器

python装饰器的定义:在代码运行期间在不改变原函数定义的基础上,动态给该函数增加功能的方式称之为装饰器(Decorator)

装饰器的优点和用途:

1. 抽离出大量函数中与函数功能本身无关的的雷同代码并继续重用。
2. 使用装饰器可以将函数“修饰”为完全不同的行为,可以有效的将业务逻辑正交分解,如用于将权限与身份验证从业务中独立出来。
3. 如果一个函数需要一个功能,且这个功能可以被使用在很多函数上,或是函数并不是自己实现,那可以写个装饰器来实现这些功能。
概况来说,装饰器的作用就是为已经存在的对象添加一些额外的功能。

在学习如何运用装饰器前我们先来学习以下几个知识点:

1.变量的作用域:

  在python中,函数会创建一个自己的作用域或称之为命名空间,结合以下示例来展示函数命名空间的作用域。

示例代码1:
#coding=utf-8 outerVar = "this is a global variable" def test() : innerVar = "this is a Local variable" print ("local variables :") print (locals()) #locals函数返回的是函数test()内部本地作用域中的可用的变量名称的字典(变量名:值) test() print ("global variables :") print (globals()) #globals函数返回的是python程序中的可用的变量名称的字典(变量名:值) #执行结果: local variables : {'innerVar': 'this is a Local variable'} global variables : {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000015848FE87F0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'test.py', '__cached__': None,'outerVar': 'this is a global variable', 'test': <function test at 0x0000015848E11E18>}

2.变量解析规则:
在python的作用域规则里面,创建变量时一定会在当前作用域里创建同样的变量,但访问或修改变量时,会在当前作用域中查找该变量,如果没找到匹配的变量,就会依次向上在闭合作用域中进行查找,所以也可以在函数中直接访问全局变量。

示例代码2:
#coding=utf-8
outerVar = "this is a global variable"
def test() :
 innerVar = "this is a Local variable"
 print (outerVar) #获取全局变量outerVar值
 print (n)        #获取全局变量n的值
 
n = 33
test()    

#执行结果:
this is a global variable
33 

3.变量的生存空间:
   变量不仅仅是存在于一个个的命名空间中,它们还都有自己的生存周期,全局变量的生存周期是在整个程序执行期间有效,而局部变量的生存周期只在当前作用域中有效,一旦这个作用域不存在了,比如函数执行退出了,变量的生存周期就结束了。

示例代码3:
#coding=utf-8
outerVar = "this is a global variable"
def test() :
 innerVar = "this is a Local variable"
test()
print (innerVar)  #test函数执行结束后,innerVar变量已释放,再访问函数内部变量就会报NameError

执行结果:
Traceback (most recent call last):
  File "test.py", line 56, in <module>
    print (innerVar)
NameError: name 'innerVar' is not defined

4.嵌套函数:
  定义:嵌套函数就是在函数里面定义函数,而且现有的作用域和变量生存周期依旧不变。

  说明:在python里,函数就是对象,它也只是一些普通的值而已。也就是说你可以把函数像参数一样传递给其他的函数或者说从函数了里面返回函数。

示例代码4:
#coding=utf-8
def outer() :
 name = "python"
 def inner() : 
#获取name变量值时,python解析器默认会先在函数内部查找,查找失败后,继续往上一层函数作用域查找。 print(name) 
#python解释器会优先在outer的作用域里面查找变量名为inner的变量。把作为函数标识符的变量inner作为返回值返回。
每次函数outer被调用的时候,函数inner都会被重新定义,如果它不被当做变量返回的话,每次执行过后它将不复存在。
return inner()  
print (outer()) 结果: python None #inner函数默认返回值为None

嵌套函数返回函数不加()表示返回函数对象,如下示例5所示:

示例代码5:
#coding=utf-8 def outer() : name = "python" def inner() : print( name) return inner #表示返回函数对象 print (outer()) 执行结果:
<function outer.<locals>.inner at 0x0000027446B6A9D8>

5.函数作为变量:
  python中函数可以作为参数来传递

示例代码6:
#coding=utf-8          
def add(x, y):         
  return x + y          
def sub(x, y):         
  return x - y          
def apply(func, x, y): 
  return func(x, y) 
     
print (apply(add, 2, 1)) #add函数作为apply函数的参数传递
print (apply(sub, 2, 1)) 

执行结果:
3
1

6.闭包:

  定义:如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包。

  一个函数返回的函数对象,这个函数对象执行需要依赖外部函数的变量值,这个时候函数返回的实际内容如下:
    1.函数对象
    2.函数对象执行需要使用的外部变量和变量值。
简而言之:闭包就是返回一个函数和一个函数执行所需要的外部变量。

示例代码7:
#coding=utf-8 def outer(): name = "python" def inner() : print (name) #函数使用了外部的name变量 return inner #返回函数对象 res = outer() #调用outer()方法,返回inner函数对象 res() #inner函数的对象+()=调用函数inner() print (res.func_closure)# 查看函数包含哪些外部变量 #print (res()) #注意使用print打印返回结果为name值+inner函数默认返回值None 执行结果: python (<cell at 0x0000000002706CD8: str object at 0x0000000002708738>,) #外部变量是一个str类型

上例中的inner()函数就是一个闭包,它本身也是一个函数,而且还可以访问本身之外的变量。

每次函数outer被调用时,inner函数都会被重新定义,示例代码7每次返回的函数inner结果都一样,因为name没变。如下例所示,我们将函数稍微改动
一下,结果就不一样了。

示例代码8:
#coding=utf-8 def outer(name) : def inner() : print (name) return inner res1 = outer("python") res2 = outer("java") res1() res2() 执行结果: python java

学习了以上6个小知识点,下面开始学习装饰器。

装饰器的定义:
  装饰器其实就是一个闭包,把一个函数当做参数后返回一个替代版函数。

 装饰器分类:

 装饰器分为无参数decorator和有参数decorator
无参数decorator:生成一个新的装饰器函数
有参数decorator:装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰。
装饰器的具体定义:
1、把要装饰的方法作为输入参数;
2、在函数体内可以进行任意的操作;
3、只要确保最后返回一个可执行的函数即可(可以是原来的输入参数函数,也可以是一个新函数)

装饰器学习七步法:

第一步:最简单的函数,准备附加额外功能

# -*- coding:utf-8 -*-
#最简单的函数,表示调用了两次函数 def myfunc(): print "myfunc() called."
myfunc() myfunc() 执行结果: myfunc() called. myfunc() called.

第二步:使用装饰函数在函数执行前和执行后分别附加额外功能

# -*- coding:utf-8 -*-
'''示例2: 替换函数(装饰) ,装饰函数的参数是被装饰的函数对象,返回原函数对象。装饰的实质语句: myfunc = deco(myfunc)'''
def deco(func):
 print ("before myfunc() called.")  #在func()函数执行前附加功能,打印一句话
 func() #被执行函数
 print ("after myfunc() called.")  #在func()函数执行后附加功能,打印一句话 
 return func
 
def myfunc():
 print ("myfunc()called.")
 
new_myfunc = deco(myfunc) #表示调用参数为myfunc函数对象的deco()函数,结果返回func函数对象并赋值给myfunc
new_myfunc() #表示调用myfunc函数
new_myfunc() #表示调用myfunc函数

结果:
before myfunc() called.
myfunc()called.
after myfunc() called.
myfunc()called.
myfunc()called.
解析:
1.myfunc = deco(myfunc)执行结果: 
 before myfunc() called.
 myfunc()called.
 after myfunc() called.
2.第一次调用myfunc()执行结果:
  myfunc()called.
3.第二次调用myfunc()执行结果:
  myfunc()called. 

 从第2和第3次调用myfunc函数来看,并没有实现每次调用都返回第1次调用的效果,那么我们要实现每次调用都带有附加功能的效果,我们后面会=逐步实现。

第三步:使用@符号来装饰函数

# -*- coding:utf-8 -*-
'''示例3:使用@符号装饰函数,相当于"myfunc = deco(myfunc)",但发现新函数只在第一次被调用,且原函数多调用了一次。等价于第二步程序 '''
def deco(func): print (
"before myfunc() called.") func() print ("after myfunc() called.") return func @deco def myfunc(): print ("myfunc()called.") myfunc() myfunc() #执行结果: before myfunc() called. myfunc()called. after myfunc() called. myfunc()called. myfunc()called.

第四步:使用内嵌包装函数来确保每次新函数都被调用

# -*- coding:utf-8 -*-
'''示例4: 使用内嵌包装函数来确保每次新函数都被调用,内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''
def deco(func):
 def _deco():
     print ("before myfunc() called.")
     func()
     print ("after myfunc() called.")
 # 不需要返回func,实际上应返回原函数的返回值
 return _deco
 
@deco
def myfunc():
 print ("myfunc() called.")
 return 'ok'
 
myfunc()
myfunc() 
执行结果:
before myfunc() called.
myfunc() called.
after myfunc() called.
before myfunc() called.
myfunc() called.
after myfunc() called.

上面是实现了1个函数使用装饰器的例子,下面演示2个函数分别使用装饰器的实例:

# -*- coding:utf-8 -*-
'''增加打印函数执行时间功能,分别统计两个函数的执行效率 '''
import time def deco(func): def _deco(): start_time=time.time() func() end_time=time.time() print("执行总耗时:",end_time-start_time) return _deco @deco def myfunc(): print (" myfunc() called.") time.sleep(1) return 'true' @deco def youyfunc(): print (" youyfunc() called.") time.sleep(2) return 'true' myfunc() print ('*'*20) youyfunc() #结果: #myfunc() called. #执行总耗时: 1.0080790519714355 #******************** # youyfunc() called. #执行总耗时: 2.0119848251342773 #执行过程解析:
执行myfunc()等价于执行deco(myfunc)() #首先myfunc函数作为参数传递给了deco()函数,形参func被替换为实参myfunc,deco()函数开始顺序执行_deco()函数, #先调用了myfunc()函数,开始执行myfunc函数,打印执行总耗时,最后返回_deco()函数对象。

 说明:使用装饰器的函数之间变量不会互相影响,等于每次调用都会重新生成一个_deco函数。
第五步:实现对带参数的函数进行装饰

# -*- coding:utf-8 -*-
''' 内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象 '''
def deco(func): def _deco(a, b): print (
"before myfunc() called.") ret = func(a, b) print ("after myfunc() called. result: %s" % ret) return ret return _deco @deco def myfunc(a, b): print ("myfunc(%s,%s) called."%(a, b)) return a + b myfunc(1, 2) #使用print (myfunc(1, 2)) 查看return ret 的结果 myfunc(3, 4) 执行结果: before myfunc() called. myfunc(1,2) called. after myfunc() called. result: 3 before myfunc() called. myfunc(3,4) called. after myfunc() called. result: 7

第六步:对参数数量不确定的函数进行装饰

# -*- coding:utf-8 -*-
'''参数用(*args, **kwargs),自动适应变参和命名参数'''
def deco(func):
     def _deco(*args, **kwargs):
         print ("before %s called." % func.__name__)
         ret = func(*args, **kwargs)
         print (" after %s called. result: %s" % (func.__name__, ret))
         return ret
     return _deco
     
@deco
def myfunc1(a, b):
     print (" myfunc(%s,%s) called." % (a, b))
     return a+b
     
@deco
def myfunc2(a, b, c):
     print (" myfunc2(%s,%s,%s) called." % (a, b, c))
     return a+b+c
     
myfunc1(1, 2)
myfunc1(3, 4)
myfunc2(1, 2, 3)
myfunc2(3, 4, 5)

#结果
before myfunc1 called.
 myfunc(1,2) called.
 after myfunc1 called. result: 3
before myfunc1 called.
 myfunc(3,4) called.
 after myfunc1 called. result: 7
before myfunc2 called.
 myfunc2(1,2,3) called.
 after myfunc2 called. result: 6
before myfunc2 called.
 myfunc2(3,4,5) called.
 after myfunc2 called. result: 12

第七步:装饰器带可变参数

# -*- coding:utf-8 -*-
'''在装饰器第四步4的基础上,让装饰器带参数和上一示例相比在外层多了一层包装。装饰函数名实际上应更有意义些'''
def deco(arg): 
def _deco(func): def __deco(): print ("before %s called [%s]." % (func.__name__, arg)) func() print ("after %s called [%s]." % (func.__name__, arg)) return __deco return _deco @deco("mymodule1") #装饰器参数是一个字符串,本身没有含义 def myfunc(): print (" myfunc() called.") @deco("mymodule2") def myfunc2(): print (" myfunc2() called.") myfunc() #调用过程等价于:deco("mymodule1")()()-->_deco()()-->__deco() myfunc2() #解析三组闭包: 1. deco("mymodule1")()()+arg-->返回_deco函数对象 2. _deco()()+arg+func -->返回__deco函数对象 3. __deco()+arg+func --返回函数最终执行结果 执行结果: before myfunc called [mymodule1]. myfunc() called. after myfunc called [mymodule1]. before myfunc2 called [mymodule2]. myfunc2() called. after myfunc2 called [mymodule2].

装饰器顺序:
当同时对一个函数使用多个不同的装饰器进行装饰时,这个时候装饰器的顺序就很重要了。
代码示例:

@A
@B
@C
def f():
   pass
等价于:
f = A(B(C(f)))

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

posted @ 2019-02-20 23:48  Conner&sun  阅读(576)  评论(0编辑  收藏  举报