Python装饰器学习
学习装饰器首先要理解闭包的概念。
在Python中,当你引用一个函数A时,函数A又引用了函数B,这个B就称为闭包。在调用A时传递的参数就叫做自由变量。 闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。
def func(name):
def inner_func(age):
print 'name:', name, 'age:', age
return inner_func
bb = func('haibo')
bb(26) # >>> name: haibo age: 26
name就是自由变量,当函数func的生命周期结束之后,name这个变量依然存在,因为它被闭包引用了,所以不会被回收。 在装饰器中就是用的闭包的概念,为了生成通用的函数或者类,以便调用。
再看下面的例子:
def singledeco(cls):
instances = {}
def wrapper(*args,**kwargs):
if not instances.has_key(cls):
instances[cls] = cls(*args,**kwargs)
return instances[cls]
return wrapper
@singledeco
class MyClass():
def __init__(self):
self.name = 'haibo'
m = MyClass()
a = MyClass()
print a is m
输出:
True
上面的instances变量在函数运行完后,并没有立即消失,而是仍然存在内存空间里。当我们第二次实例化的时候,直接就用了之前的实例。
当一个内嵌函数引用其外部作作用域的变量,我们就会得到一个闭包. 总结一下,创建一个闭包必须满足以下几点:
必须有一个内嵌函数
内嵌函数必须引用外部函数中的变量
外部函数的返回值必须是内嵌函数
有一道比较有意思的题:
def deco():
return (lambda x:i*x for i in range(3))
f1,f2,f3 = deco()
print f1(2),f2(2),f3(2)
输出:4 4 4
网上说了这是Python闭包的一个特点,也是Python的Lazy-loading的特点。之所以会出现这种结果,我觉得还是和对象引用以及LEGB准则说起:
当执行f1,f2,f3=deco()语句时,返回的事闭包(即内嵌函数)的地址,而当我们调用闭包时,内嵌函数的参数是传入的引用,会根据LEGB准则搜索变量i,内部没有变量,就搜索外部变量,此时变量i指向对象2,所以最后输出的结果是4 4 4。
可能你想输出0 2 4,可做如下修改:
def deco():
return (lambda x,j=i:j*x for i in range(3))
f1,f2,f3 = deco()
print f1(2),f2(2),f3(2)
这样修改就会输出0 2 4。
我的理解:j是内部函数的局部变量,比如当i=1时,j这个局部变量和i都指向了对象1,当变量i改变后,j的引用却不变。当调用闭包后,LEGB搜索内部变量,就搜到了j。
Python的装饰器主要采用设计模式中的装饰器模式、代理模式。即通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的。 Python中可调用的对象可以是函数,方法以及含有__call__的类。
Python实现代理的简单例子:
def decorator(f):
def wrapper(*args):
z = f(*args)
cache = {}
cache.setdefault('z',z)
print 'the result is %d' % cache['z']
return z
return wrapper
def Add(x,y):
return x + y
Add = decorator(Add)
Add(1,2)
decorator函数是一个代理函数,仅接受被代理函数作为参数,真正的封装动作是由wrapper函数完成,执行decorator(Add)语句后会返回wrapper函数,Add(1,2)相当于执行wrapper(1,2)函数。这个例子就实现了基本的装饰器模式。由于Add = decorator(Add)看起来有点重复,因此Python出现了装饰器,让装饰模式看起来更简洁。
@decorator
def Add(x,y):
return x + y
这个和上面的Add = decorator(Add)是等价的。
上面的例子是装饰器本身不需要带参数的,如果装饰器本身是带参数的,那么就要写三阶的嵌套函数了。
def print_text(name):
def decorator(f):
def wrapper(*args):
z = f(*args)
cache = {}
cache.setdefault('z',z)
print 'the result is %d' % cache['z']
print 'the decorator text is %s' % name
return z
return wrapper
return decorator
@print_text('haibo')
def Add(x,y):
return x + y
Add(1,2)
输出:
the result is 3
the decorator text is haibo
它相当于print_text('haibo')(Add),其中print_text('haibo') 返回decorator函数对象,后面的就和二阶嵌套相同。
说完装饰器的定义还有一点要补充,在Python中一切皆对象,那么函数也是一种对象,也有自己的属性,如 name 。当函数被装饰器装饰之后,其__name__属性将发生变化,变成封装函数属性,如上面例子Add.__name__输出为wrapper,如果我们想将装饰器做得完美一点,保留函数所有属性,就可以借助functools模块中的wrapper装饰器,使用它,会保留所有被装饰函数的属性。例子:
from functools import wrapper
def print_text(name):
def decorator(f):
@wrapper(f)
def wrapper(*args):
z = f(*args)
cache = {}
cache.setdefault('z',z)
print 'the result is %d' % cache['z']
print 'the decorator text is %s' % name
return z
return wrapper
return decorator
@print_text('haibo')
def Add(x,y):
return x + y
Add.__name__
输出Add
Python内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多。所以就说下property的用法:
property原型:class property([fget[, fset[, fdel[, doc]]]])
class test(object):
@property
#the class has attribute score ,it is read only
def score(self):
return self._score
@score.setter
def score(self,value):
#set the score
print 'set score to be value'
self._score = value
@score.setter
def score(self,value):
del self._score
@property
def value(self):
return 30-self._score
a = test()
a.score = 3
print a.score
print a.value
del a.score
上述例子中a.score=3会调用 fset方法,即setter,执行del score 会调用 fdel,即deleter。
如果执行a.value = 5 此时会出现:
Traceback (most recent call last):
File "C:/Python27/s.py", line 24, in <module>
a.value = 5
AttributeError: can't set attribute
因为此时value为只读。
微信分享/微信扫码阅读