现在的位置: 首页 > 综合 > 正文

Decorator的另一种实现方式

2013年08月18日 ⁄ 综合 ⁄ 共 3876字 ⁄ 字号 评论关闭
这一切都源自python-cn邮件列表上的一个话题,这个话题中,leopay给出了另一种实现decorator的方式:
一般decorator都是写一个嵌套函数,
def A(func):
    def new_func(*args, **argkw):
        #做一些额外的工作
        return func(*args, **argkw) #调用原函数继续进行处理
    return new_func
@A
def f(args):pass


其实也有另外一种"优雅"点的写法:
def A(func, *args, **argkw):
    #做一些额外的工作
    return func(*args, **argkw) #调用原函数继续进行处理

@A.__get__
def f(args):pass

很有趣,对吧。我分析了一下这种方式起作用的原因,很有意思,特记录如下。

Descriptor
        从Python中最简单也最常用的语义表达方式——属性访问——开始。形如obj.name这样的表达形式就是一个属性访问。在Python编译后的结果中,其对应的字节码指令为LOAD_ATTR。属性访问看上去就是一个简单的操作,但是从概念上来说,它分为两个相对独立的部分:
1、属性搜索
2、属性获取
        属性搜索就是在对象obj以及obj的各个基类的__dict__中搜索符号“name”,当然,这个搜索是有一定顺序的,特别是当基类有多个时,这里不过多深入。一旦发现了符号,就结束了“属性搜索”阶段,从而进入“属性获取”阶段。
        最原始的属性获取就是直接取值就好了,比如 value = obj.name,obj中的name对应的是什么东西,value最后也就是什么东西,这种“属性获取”的方式在各种编程语言中都存在,比如Java,C++。这种方式简单,但是不够灵活,比如有一天,我们想要value = obj.name的语义变成“如果name为空,返回一个特殊字符串;否则,返回name本身”。Java和C++无法轻松处理这种潜在的变化,当然,我们可以将代码中所有obj.name改成obj.getName,通过函数完成name的检查,但是一旦工程基本成型后,这种改动的工作量可想而知。所以在Java或C++中,推荐的方式都是将数据设为private,通过public的函数访问属性,当“属性获取”的语义发生改变时,则只需要改变函数即可。比如下面这种方式:

class A {
    
private String name;
    
public getName() {
        
return name;
    }

}

        这种方式能够工作,但这意味着,从一开始,我们就必须通过函数包装所有属性,一来这使代码量大大增加;而来obj.getName的表达形式肯定不如obj.name来得自然。所以有了第二种方式,有没有办法,使得只改变class中的一处代码,就使得所有引用obj.name的地方都透明地从简单的赋值语义变为函数调用语义。C#实现了这种方式,Python也通过Descriptor实现了这种方式。
简单地说,当Python在属性搜索结束后,发现符号"name"对应的对象name_obj是一个特殊的descriptor对象时,“属性获取”的语义就从直接返回name_obj变成了return name_obj.__get__(obj, type(obj))。那么什么是一个descriptor对象呢,很简单,一个实现了__get__, __set__, __del__三个特殊函数的class的实例对象就是一个descriptor对象。下面给出一个简单的例子:

class Desc(object):
    
def __init__(self, value):
        self.value 
= value
        
    
def __get__(self, obj, type):
        
print 'descriptor change something'
        
return self.value
        
class A(object):
    
pass
    
= A()
A.value 
= Desc(1)
print a.value

输出的结果是:
descriptor change something
1

作为一种Descriptor的函数
        有意思的是,一个实现了__get__的class的对象,只有当它是某个class的属性时,才是descriptor,当它是某个instance的属性时,则不是descriptor。比如上面的A.value = Desc(1),换成a.value = Desc(1),那么输出的结果就是<__main__.Desc object at 0x00B46530>了。现在我们可以从另外一个角度来看待函数了,函数是一种descriptor对象,这意味着函数的类中实现了__get__操作。函数还有类?千真万确,在Python中,所有的一切都是对象,所以函数也是一种instance,它的类在Python的源码中对应的是PyFunctionObject。通过下面的代码,我们可以观察到这个PyFunctionObject确实是一个descriptor。

import types
print types.FunctionType.__get__

        输出:<slot wrapper '__get__' of 'function' objects>,实际上对应的是Python源码中的func_descr_get函数。这说明PyFunctionObject确实是一个descriptor,为什么它必须要是一个descriptor呢?答案是为了OO。

function与method
        Python中的OO是建立在function的基础上的,这一点跟C++的对象模型很类似。考虑下面的类的定义:

class A(object):
    
def show(self, name):
        pirnt 
'hello ', name

        其中的show仅仅是一个简单的函数,但是你永远不能直接访问这个函数,因为你要访问show,只有两种方式:
        1、a = A(); a.show('robert')
        2、A.show(A(), 'robert')
        无论是a.show,还是A.show,注意,它们都是“属性访问”了。而show这个函数是一种descriptor对象,所以,无论是a.show,还是A.show,都会激发PyFunctionType.__get__(show, obj, type),对于a.show来说,obj,type分别是a和A;而对于A.show来说,obj, type分别为None和A。
        但是不管如何,这时我们得到的已经不仅仅是一个函数了,而是经过__get__转换的结果,这个结果是什么呢,是一个method对象,简单地说,这个对象就是将函数,类和实例对象绑定在一起之后得到的一个对象,其中有三个属性,im_func, im_self和im_class,对于PyFunctionType.__get__(show, obj, type)的调用结果而言,存在以下的对应关系:(im_func=show, im_self=obj, im_class=type)。通过descriptor的转换,本来跟谁都没有关系的function,就跟某个对象关联在一起了,这个就是“绑定”。显然,method对象也就一个可调用的对象,当我们进行method(*args, **argkw)的调用时,会被Python转换成im_func(im_self, *args, **argkw)这样的调用,从而实现了OO中“成员函数”的语义。

Decorator
        decorator实际上是一种对函数的修饰,比如

def A():
    
pass
   
@A
def B():
    
pass

        编译之后,实际会增加一条对B的动作:B = A(B)。为了将B保存在A的上下文环境中,就使得A必须采用closure的方式。
        但是我们看到,实现decorator的关键是保存B,即保存一个函数,我们在上面看到,method对象中恰恰保存了一个函数,于是这一切就顺理成章了。
        在之前我们看到descriptor的__get__动作的激发都是由“属性访问”自动激发的,但是我们可以通过A.__get__直接手动激发,所以A.__get__(B)就返回了一个method对象,其中保存了关键的函数B。当我们进行A.__get__(B)(*args, **argkw)这样的调用时,就转换成了A(B, *args, **argkw),本来B这个位置是留给某个实例对象,从而完成“成员函数”语义,实现OO的,但是这仅仅是一种协议,B所占据的位置实际上可以传递进去任意的对象,从而完成任意的操作。
        最后我们写出了:

@A.__get__
def B(*args, **argkw):
    
pass

        这样的形式,让Python编译结果自动为我们完成B = A.__get__(B)的动作,一种“优雅”的decorator形式就诞生了。

抱歉!评论已关闭.