Python装饰器

1.闭包

2.Python装饰器

3.类的装饰器


一、闭包

学习装饰器首先要理解闭包的概念。

在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的装饰器主要采用设计模式中的装饰器模式、代理模式。即通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的。 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为只读。

三、类的装饰器

类方法的装饰

注意类方法的装饰和普通函数不同,方法还有第一个参数self,即类实例。

下面是一个例子,比较好理解。我这个装饰器主要是把scrapy爬取的类变成Python字典,存储到数据库中。

def decorator(method):
    def to_dict(decorated_instance, item):
        result = {}
        for key in item:
            result[key] = item[key]
            if isinstance(item[key], CompanyItem):
                company = {}
                for ele in result[key]:
                    company[ele] = result[key][ele]
                result[key] = company
        method(decorated_instance,result)

    return to_dict



class Deco(object):

    @decorator
    def deco(self,item):
        for key in item:
            print key, item[key]


if __name__== "__main__":

    s = Deco()
    s.deco({'s':3})

也就是说,类的方法必须要带有一个类实例本身的参数。

装饰类

上面介绍了很多装饰函数的装饰器,下面介绍一下装饰类的装饰器。其实这和装饰函数差不多,只不过是被装饰的是类,返回的是一个类,而不是一个函数。

def decorator(oneclass):
    class newclass():
        def __init__(self,name):
            self.name = name
            self.total = 0
            self.wrapper = oneclass(name)

        def visit(self):
            self.total += 1
            print 'the wrapped class print the totoal %d' % self.total
            self.wrapper.visit()
    return newclass


@decorator
class A(object):
    def __init__(self,name):
        self.name = name

    def visit(self):
        print 'the original class'



s = A('haibo')
s.visit()

类装饰器

上面不管装饰类还是装饰函数,装饰器本身是一个函数,现在说说类装饰器。

class decorator(object):
def __init__(self,desp='function'):
    self.desp = desp

def __call__(self, func):
    def wrapped(*args,**kwargs):
        print self.desp
        func(*args,**kwargs)
    return wrapped


@decorator()
def func(*args):
    print args

func(3)

上面的代码有几点需要注意:

1、如果装饰器本身带参数,那装饰函数的时候不管你写不写参数都要加上括号,否则不能正常运行; 
2、类装饰器是通过 init ()和 call ()来实现。

上面的例子是装饰器本身带参数,被装饰的函数也带参数,再看个例子。

class decorator(object):
    def __init__(self,func):
        self.func = func

    def __call__(self,*args):
        return self.func(*args)


@decorator
def func(*args):
    print args

func(3)

装饰器的调用顺序:装饰器的调用顺序与@ 语法声明的顺序正好相反。

@deco1
@deco2
def func():
   .....

上面的装饰相当于:func()=deco1(deco2(func))。


展开阅读全文

没有更多推荐了,返回首页