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为只读。

--------EOF---------
微信分享/微信扫码阅读