Python 装饰器(Decorator)
装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。下面就一步步看看Python中的装饰器。
装饰器本身是一个Python函数,他可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个额外的(函数)对象。
什么时候使用装饰器?
一般在开发过程中,要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,这时就要使用装饰器了。
使用装饰器来修饰函数
- 不改变原有的程序,并且可以添加新的功能
- 可以提高程序的可重复利用性,并增加了程序的可读性。
要了解装饰器,需要了解一些概念
作用域
在python中,作用域分为两种:全局作用域和局部作用域。
全局作用域是定义在文件级别的变量,函数名。而局部作用域,则是定义函数内部。
关于作用域,我们要理解两点:
a.在全局不能访问到局部定义的变量
b.在局部能够访问到全局定义的变量,但是不能修改全局定义的变量(当然有方法可以修改)
关于作用域的问题,只需要记住两点就行:
全局变量能够被文件任何地方引用,但修改只能在全局进行操作;如果局部没有找到所需的变量,就会往外进行查找,没有找到就会报错。
闭包
装饰器
装饰器:外部函数传入被装饰函数名,内部函数返回装饰函数名。
特点:1.不修改被装饰函数的调用方式 2.不修改被装饰函数的源代码
实例:
1# FileName : PyDecorator.py 2# Author : Adil 3# DateTime : 2018/4/19 19:04 4# SoftWare : PyCharm 5 6''' 7装饰器 8''' 9 10def outer(some_func): # 将foo函数(地址)赋值给some_func 11 def inner(): 12 print("before some_func") 13 ret = some_func() # 这里调用foo 返回 1 14 print(ret) 15 return ret + 1 # 返回 ret + 1 = 1 + 1 = 2 16 return inner # 返回 inner 函数(地址) 17 18def foo(): 19 return 1 20 21decorated = outer(foo) # 将函数 foo 作为参数传给 outer ,返回 inner 函数对象(函数地址)。 22print(decorated) # 一个替代版函数,函数地址 23print(decorated()) # 返回 ret + 1 = 1 + 1 = 2
函数装饰器 @ 符号的应用
Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作;
1@outer # 替代后后面的操作 decorated = outer(foo) # 将函数 foo 作为参数传给 outer ,返回 inner 函数对象(函数地址)。 2def foo(): 3 print("foo") 4 return 1
在上面的例子里我们是将原本的方法用装饰后的方法代替:
1def outer(some_func): # 将foo函数(地址)赋值给some_func 2 def inner(): 3 print("before some_func") 4 ret = some_func() # 这里调用foo 返回 1 5 print(ret) 6 return ret + 1 # 返回 ret + 1 = 1 + 1 = 2 7 return inner # 返回 inner 函数(地址) 8 9@outer 10def foo(): 11 print("foo") 12 return 1 13 14foo() 15print(foo()) 16 17 18 19# 执行结果 20# before some_func 21# foo 22# 1 23# before some_func 24# foo 25# 1 26# 2
被装饰的函数有参数
1def decoratorF(funa): 2 def middle(param): 3 print(param,end=',') 4 funa(param) 5 return middle 6 7@decoratorF 8def testF(param): 9 print("我是 Yang") 10 11testF("Hello") 12 13# 执行结果 14# Hello,我是 Yang
更通用的装饰器
有了这招新的技能,我们随随便便就可以写一个能够记录下传递给函数参数的装饰器了。先来个简单地把日志输出到界面的例子:
含多个参数的装饰器
1def testL(funa): 2 3 def inner(*args,**kwargs): 4 print("paramers are : %s,%s" %(args,kwargs)) 5 return funa(*args, **kwargs) 6 return inner 7 8@testL 9def testf(x,y,**k): 10 print("testf") 11 12testf('a','b',z='c',t= 'x') 13 14# 打印结果 15# paramers are : ('a', 'b'),{'z': 'c', 't': 'x'} 16# testf
一个函数被多个装饰器装饰,执行顺序从下往上。
1def timer(funt): 2 def get_time(*args,**kwargs): 3 start_time = time.time() 4 funt(*args, **kwargs) 5 stop_time = time.time() 6 print('execute time is %s'%(stop_time-start_time)) 7 return get_time 8 9 10 11def testL(funa): 12 13 def inner(*args,**kwargs): 14 print("paramers are : %s,%s" %(args,kwargs)) 15 return funa(*args, **kwargs) 16 return inner 17 18@timer 19@testL 20def testf(x,y,**k): 21 print("testf") 22 23testf('a','b',z='c',t= 'x') 24 25# 打印结果 26# paramers are : ('a', 'b'),{'z': 'c', 't': 'x'} 27# testf 28# execute time is 0.0
类装饰器
装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。
使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。
在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。
1class ClassTest(object): 2 def __init__(self,func): 3 self._func =func 4 5 def __call__(self): 6 print('class decorator running') 7 self._func() 8 print('class decorator ending') 9 10@ClassTest # fun_Test = ClassTest(fun_Test) 11def fun_Test(): 12 print('fun_Test') 13 14fun_Test() # ClassTest(fun_Test)() 15 16# 执行结果 17# class decorator running 18# fun_Test 19# class decorator ending 20 21 22class TestClass(object): 23 def __init__(self): 24 pass 25 def __call__(self, func): 26 def _call(*args,**kwargs): 27 print('class decorator running') 28 func(*args,**kwargs) 29 return func(*args,**kwargs) 30 return _call 31 32class Bar(object): 33 @TestClass() 34 def bar(self,test,ids): # bar = TestClass()(bar) 35 print('bar') 36 37Bar().bar('Yang','123') 38 39# 执行结果 40# class decorator running 41# bar 42# bar 43 44class Fee(object): 45 def __init__(self,func): 46 self._func = func 47 48 def __call__(self, name): 49 print 'hello', 50 self._func(name) 51 52@Fee 53def foo(gold): 54 print gold, 55 print 'lxshen' 56 57foo('大家好') 58 59输出: 60hello 大家好 lxshen 61 62分析: 63#1.当用Fee来装作装饰器对foo函数进行装饰的时候,首先会创建Fee的实例对象 64# 并且会把foo这个函数名当做参数传递到__init__方法中 65# 即在__init__方法中的func变量指向了foo函数体 66#2. foo函数相当于指向了用Fee创建出来的实例对象 67# 68#3. 当在使用foo()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法 69# 70#4. 为了能够在__call__方法中调用原来foo指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用 71# 所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到foo之前的函数体
Python内置装饰器
在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。
- staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
- classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
- property 是属性的意思,表示可以通过通过类实例直接访问的信息