Differences between revisions 2 and 3
Revision 2 as of 2008-04-19 08:45:38
Size: 2625
Editor: ZoomQuiet
Comment:
Revision 3 as of 2008-04-20 01:35:06
Size: 10011
Editor: ZoomQuiet
Comment:
Deletions are marked like this. Additions are marked like this.
Line 7: Line 7:


Line 43: Line 40:
== 研究原因 == == 原因初探 ==
Line 94: Line 91:
== 机制深究 ==
{{{
Robert Chen <[email protected]>
reply-to [email protected],
to [email protected],
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__
}}}
输出:
{{{
<slot wrapper '__get__' of 'function' objects>
}}},实际上对应的是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形式就诞生了。

TableOfContents

Include(ZPyUGnav)

优雅型修饰

decorator的另一种写法 {{{from leopay <[email protected]> reply-to [email protected], to [email protected], date Wed, Apr 16, 2008 at 11:43 PM subject [CPyUG:47631] decorator的另一种写法 }}}

一般decorator都是写一个嵌套函数,

   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

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

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

原因初探

{{{yuting cui <[email protected]> reply-to [email protected], to [email protected], 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 <[email protected]>
reply-to        [email protected],
to      [email protected],
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对象。

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

   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

  • 比如上面的A.value = Desc(1),换成a.value = Desc(1),那么输出的结果就是

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

  • 现在我们可以从另外一个角度来看待函数了,函数是一种descriptor对象,这意味着函数的类中实现了__get__操作。

  • 函数还有类?
    • 千真万确!
    • 在Python中,所有的一切都是对象,所以函数也是一种instance,它的类在Python的源码中对应的是PyFunctionObject

    • 通过下面的代码,我们可以观察到这个PyFunctionObject确实是一个descriptor。

   1 import types
   2 print types.FunctionType.__get__

输出:

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

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

  • 答案是为了OO

function与method

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

   1 class A(object):
   2     def show(self, name):
   3         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所占据的位置实际上可以传递进去任意的对象,从而完成任意的操作。

最后我们写出了:

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

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


反馈

创建 by -- ZoomQuiet [DateTime(2008-04-17T08:24:11Z)]

PageComment2

[:/PageCommentData:PageCommentData]

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