##language:zh #pragma section-numbers off ##含有章节索引导航的 ZPyUG 文章通用模板 <> ## 默许导航,请保留 <> ##startInc = 优雅型修饰 = `decorator的另一种写法` {{{from leopay reply-to python-cn@googlegroups.com, to python-cn@googlegroups.com, date Wed, Apr 16, 2008 at 11:43 PM subject [CPyUG:47631] decorator的另一种写法 }}} 一般decorator都是写一个嵌套函数, {{{#!python def A(func): def new_func(*args, **argkw): #做一些额外的工作 return func(*args, **argkw) #调用原函数继续进行处理 return new_func @A def f(args):pass }}} 其实也有另外一种"优雅"点的写法: {{{#!python def A(func, *args, **argkw): #做一些额外的工作 return func(*args, **argkw) #调用原函数继续进行处理 @A.__get__ def f(args):pass }}} == 原因初探 == {{{yuting cui reply-to python-cn@googlegroups.com, to python-cn@googlegroups.com, date Sat, Apr 19, 2008 at 2:30 AM subject [CPyUG:48006] Re: [CPyUG:/] Re: decorator的另一种写法 }}} 简单看了一下,对于一个函数来说,它的默认的`__get__`方法是把实例和这个函数绑定 比如说:{{{ py>>> class A(object): def __init__(self): pass py>>> A.__dict__['__init__'] py>>> a=A() py>>> a.__dict__['__init__'] Traceback (most recent call last): File "", line 1, in a.__dict__['__init__'] KeyError: '__init__' py>>> a.__init__ > }}} 这个地方按照Python2.5参考手册3.4.2.3所说{{{ 相当于调用type(a).__dict__['__init__'].__get__(a,type(a)) 为了达到这个目的,function的默认__get__ 就是把给定参数和该函数绑定并返回一个实例方法(instancemethod) 这样在你调用a.__init__这个instancemethod的时候, 就相当于把a作为第一个参数并在后面按顺序添加其他的参数之后传递给 __init__这个function }}} 于是,前面那个特殊写法的decorator就很好理解了{{{ @A.__get__ def f(args):pass }}} 这样在调用f(args)的时候,就相当于 {{{ 调用A.__get__(f)(args) 而A.__get__(f)按上述说法相当于 一个把f绑定在第一个参数的A 这样A.__get__(f)(args)就等于A(f,args) }}} `完毕` == 机制深究 == {{{ Robert Chen reply-to python-cn@googlegroups.com, to python-cn@googlegroups.com, date Sat, Apr 19, 2008 at 10:12 PM subject [CPyUG:48074] Re: [CPyUG:/] Re: decorator的另一种写法 }}} === 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对象。 下面给出一个简单的例子: {{{#!python 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() 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。 {{{#!python import types print types.FunctionType.__get__ }}} 输出: {{{ }}},实际上对应的是Python源码中的func_descr_get函数。 这说明 !PyFunctionObject 确实是一个descriptor,为什么它必须要是一个descriptor呢? * 答案是为了`OO`。 === function与method === Python中的OO是建立在function的基础上的,这一点跟C++的对象模型很类似。考虑下面的类的定义: {{{#!python 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所占据的位置实际上可以传递进去任意的对象,从而完成任意的操作。 最后我们写出了:{{{#!python @A.__get__ def B(*args, **argkw): pass }}} 这样的形式,让Python编译结果自动为我们完成`B = A.__get__(B)`的动作,一种"优雅"的decorator形式就诞生了。 == 严格区分 == {{{vcc reply-to python-cn@googlegroups.com, to python-cn@googlegroups.com, date Sat, Apr 19, 2008 at 11:32 PM subject [CPyUG:48084] Re: [CPyUG:/] Re: decorator的另一种写法 }}} 严格说起来这两种方法并不等价,因为在标准的decorator写法: {{{ @A def B():pass }}}中是调用了A函数来返回一个函数给B; 而{{{ @A.__get__ def B():pass }}}并没有调用A函数,虽然也得到了一个新函数,但是这时没有调用A函数,所以表现是不同(这意味着本来A这时可以做的一些工作不能做了)。 从这个意义上说,`__get__`的方法并不是decorator的另一种写法 === 结语 === {{{Robert Chen reply-to python-cn@googlegroups.com, to python-cn@googlegroups.com, date Sun, Apr 20, 2008 at 12:07 AM subject [CPyUG:48089] Re: [CPyUG:/] Re: decorator的另一种写法 }}} 当然,`__get__`与正式的decorator语法本质上根本不同, * 标准的decorator利用了Python中closure的特性, * 而`__get__`则是通过instance method object,内部实现截然不同。 然而decorator诞生的初始动力就是提供一种pythonic的修饰函数功能的语法,从`duck type`的意义上来说,但凡能实现这一点的,称之为decorator也不会引起地球爆炸,宇宙毁灭 :) == 呜乎 == 这么精彩的智慧激荡,实在少见! 收之藏之,分享大众! ##endInc ---- '''反馈''' 创建 by -- ZoomQuiet [<>]