优雅型修饰

decorator的另一种写法 {{{from leopay <leopay@gmail.com> 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都是写一个嵌套函数,

Toggle line numbers
   1 def A(func):
   2     def new_func(*args, **argkw):
   3         #做一些额外的工作
   4         return func(*args, **argkw) #调用原函数继续进行处理
   5     return new_func
   6 @A
   7 def f(args):pass

其实也有另外一种"优雅"点的写法:

Toggle line numbers
   1 def A(func, *args, **argkw):
   2     #做一些额外的工作
   3     return func(*args, **argkw) #调用原函数继续进行处理
   4 
   5 @A.__get__
   6 def f(args):pass    

原因初探

{{{yuting cui <yutingcui@gmail.com> 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__']
<function __init__ at 0x014A77F0>

py>>> a=A()
py>>> a.__dict__['__init__']

Traceback (most recent call last):
 File "<pyshell#47>", line 1, in <module>
   a.__dict__['__init__']
KeyError: '__init__'

py>>> a.__init__
<bound method A.__init__ of <__main__.A object at 0x014AA670>>

这个地方按照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 <search.pythoner@gmail.com>
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。属性访问看上去就是一个简单的操作,但是从概念上来说,它分为两个相对独立的部分:

最原始的属性获取就是直接取值就好了,比如

value = obj.name

,obj中的name对应的是什么东西,value最后也就是什么东西,这种"属性获取"的方式在各种编程语言中都存在,比如Java,C++。

这种方式能够工作,但这意味着,

所以有了第二种方式,使得只改变class中的一处代码,就使得所有引用obj.name的地方都透明地从简单的赋值语义变为函数调用语义。C#实现了这种方式,Python也通过Descriptor实现了这种方式。

下面给出一个简单的例子:

Toggle line numbers
   1 class Desc(object):
   2     def __init__(self, value):
   3         self.value = value
   4        
   5     def __get__(self, obj, type):
   6         print 'descriptor change something'
   7         return self.value
   8 
   9        
  10 class A(object):
  11     pass
  12    
  13 a = A()
  14 A.value = Desc(1)
  15 print a.value

输出的结果是:

descriptor change something
1

作为Descriptor的函数

有意思的是,一个实现了__get__的class的对象, 只有当它是某个class的属性时,才是descriptor,当它是某个instance的属性时,则不是descriptor

{{{<main.Desc object at 0x00B46530> }}}了。

Toggle line numbers
   1 import types
   2 print types.FunctionType.__get__

输出:

<slot wrapper '__get__' of 'function' objects>

,实际上对应的是Python源码中的func_descr_get函数。这说明 PyFunctionObject 确实是一个descriptor,为什么它必须要是一个descriptor呢?

function与method

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

Toggle line numbers
   1 class A(object):
   2     def show(self, name):
   3         pirnt 'hello ', name

其中的show仅仅是一个简单的函数,但是你永远不能直接访问这个函数,因为你要访问show,只有两种方式:

无论是a.show,还是A.show,注意:

但是不管如何,这时我们得到的已经不仅仅是一个函数了,而是经过__get__转换的结果,这个结果是什么呢,是一个method对象,简单地说,

Decorator 再用

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

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

编译之后,实际会增加一条对B的动作:B = A(B)。为了将B保存在A的上下文环境中,就使得A必须采用closure的方式。 但是我们看到,实现decorator的关键是保存B,即保存一个函数,我们在上面看到,method对象中恰恰保存了一个函数,于是这一切就顺理成章了。

最后我们写出了:

Toggle line numbers
   1 @A.__get__
   2 def B(*args, **argkw):
   3     pass

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

严格区分

{{{vcc <vcc@163.com> 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 <search.pythoner@gmail.com> 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诞生的初始动力就是提供一种pythonic的修饰函数功能的语法,从duck type的意义上来说,但凡能实现这一点的,称之为decorator也不会引起地球爆炸,宇宙毁灭 :)

呜乎


反馈

创建 by -- ZoomQuiet [2008-04-17 08:24:11]

MiscItems/2008-04-17 (last edited 2009-12-25 07:14:50 by localhost)