Python中的Descriptor描述符学习教程
|
Descriptor是什么?简而言之,Descriptor是用来定制访问类或实例的成员的一种协议。额。。好吧,一句话是说不清楚的。下面先介绍一下Python中成员变量的定义和使用。
class Cclass
{
int I;
void func();
};
Cclass c;
在上面的定义中,C++定义了一个类型,所有该类型的对象都包含有一个成员整数i和函数func;而Python则创建了一个名为Pclass、类型(__class__)为type(详情请参见MetaClass,Python中一切皆为对象,类型也不例外)的对象,然后再创建一个名为p、类型为Pclass的对象。如下所示: In [71]: type(pclass) Out[71]: <type 'type'> In [72]: p = pclass() In [73]: type(p) Out[73]: <class '__main__.pclass'> p和Pclass各自包含了一些成员,如下所示: 其中,带有双下划线的成员为特殊成员,或者可以称之为固定成员(和__slots__定义的成员类似),这些成员变量的值可以被改变,但不能被删除(del)。其中,__class__变量为对象所属的类型,__doc__为对象的文档字符串。有一个特殊成员值得注意:__dict__,该字典中保存了对象的自定义变量。相信大家在初学Python对于其中对象可以任意增加删除成员变量的能力感到惊讶,其实这个功能的玄机就在于__dict__成员中(注意type的__dict__为dictproxy类型):
In [10]: p.x = 2
In [11]: p.__dict__
Out[11]: {'x': 2}
通过上面的演示可以很清楚地看出:Python将对象的自定义成员以键值对的形式保存到__dict__字典中,而前面提到的类型定义只是这种情况的语法糖而已,即上面的类型定义等价于以下形式的定义: Class Pclass(object): pass Pclass.i = 1 Pclass.f = lambda x: x 访问成员变量时,Python也是从__dict__字典中取出变量名对应的值,如下形式的两种访问形式是等价的――在Descriptor被引入之前: p.i p.__dict__['i'] Descriptor的引入即将改变上面的规则,且看下文分解。 1.object.__get__(self,instance,owner) 成员被访问时调用,instance为成员所属的对象、owner为instance所属的类型 2.object.__set__(self,value) 成员被赋值时调用 3.0object.__delete__(self,instance) 成员被删除时调用 如果我们需要改变一个对象在其它对象中的访问规则,需要将其定义成Descriptor,之后在对该成员进行访问时将调用该Descriptor的相应函数。下面是一个使用Descriptor改变访问规则的例子:
class MyDescriptor(object):
def __init__(self,x):
self.x = x
def __get__(self,owner):
print 'get from descriptor'
return self.x
def __set__(self,value):
print 'set from descriptor'
self.x = value
def __delete__(self,instance)
print 'del from descriptor,the val is',self.x
class C(object):
d = MyDescriptor('hello')
>> C.d
get from descriptor
>> c = C()
>> c.d
get from descriptor
>> c.d = 1
set from descriptor
>> del c.d
del from descriptor,the val is 1
从例子中可以看出:当我们对对象成员进行引用(Reference)、赋值(Assign)和删除(Dereference)操作时,如果对象成员为一个Descriptor,则这些操作将执行该Descriptor对象的相应成员函数。以上约定即为Descriptor协议。 obj.name背后的魔法 Overriding或Data:对象同时提供了__get__和__set__方法 Nonoverriding或Non-Data:对象仅提供了__get__方法 (__del__方法表示自己被忽略了,很伤心~) 下面是从一个类对象中访问其成员(如C.name)的规则: 如果“name”在C.__dict__能找到,C.name将访问C.__dict__['name'],假设为v。如果v是一个Descriptor,则返回type(v).__get__(v,None,C),否则直接返回v; 如果“name”不在C.__dict__中,则向上查找C的父类,根据MRO(Method Resolution Order)对C的父类重复第一步; 还是没有找到“name”,抛出AttributeError异常。 从一个类实例对象中访问其成员(如x.name,type(x)为C)要稍微复杂一些: 如果“name”能在C(或C的父类)中找到,且其值v为一个Overriding Descriptor,则返回type(v).__get__(v,x,C)的值; 否则,如果“name”能在x.__dict__中找到,则返回x.__dict__['name']的值; 如果“name”仍未找到,则执行类对象成员的查找规则; 如果C定义了__getattr__函数,则调用该函数;否则抛出AttributeError异常。 成员赋值的查找规则与访问规则类似,但还是有一点区别:对类成员执行赋值操作时将直接设置C.__dict__中的值,而不会调用Descriptor的__set__函数。 以上面的代码为例,当访问C.d时,Python将在C.__dict__中找到d,并且发现d是一个Descriptor,因此将调用d.__get__(None,C);当访问c.d时,Python首先查找C,并且在其中发现d的定义,且d为一个Overriding Descriptor,因此执行d.__get__(c,C)。 前面介绍了Descriptor的一些细节,那么Descriptor的作用是什么呢?在Python中,Descriptor主要用来实现一些Python本身的功能,如类方法调用、staticmethod和Property等。下面将对这些使用Descriptor进行类方法调用的实现进行介绍。 Bound & Unbound Method 根据对象成员访问规则获取函数对象; 用函数对象执行函数调用; 为了验证上述过程,我们可以执行以下代码:
Class C(object):
def f(self):
pass
>> fun = C.f
Unbound Method
>> fun()
>> c = C()
>> fun = c.f
Bound Method
>> fun()
我们可以看到C.f和c.f返回了instancemethod类型的对象,这两个对象也是可调用的,但是却不是我们本以为的func对象。那么instancemethod对象和func对象之间具有什么关联呢? func类型:func类型为Python中原始的函数对象类型,即def f(): pass将定义一个func类型的对象f; instancemethod:func的一个wrapper,如果类方法没有绑定到对象,则该instancemethod为一个Unbound Method,对Unbound Method的调用将导致TypeError错误;如果类方法绑定到了对象,则该instancemethod为一个Bound Method,对Bound Method的调用不许要指定self参数的值。 如果查看Unbound Method对象和Bound Method对象的成员,我们可以发现它们都包含了一下三个成员:im_func、im_self和im_class。其中im_func为所封装的func对象,im_self则为所绑定对象的值,而im_class则为定义该函数的类对象。由此我们可以知道,Python会根据不同的情况返回函数的不同wrapper,当通过类对象访问函数时,返回的是名为Unbound Method对象的Wrapper,而通过类实例访问函数是,返回的则是绑定了该实例的名为Bound Method对象的Wrapper。 现在是Descriptor大显身手的时候了。 Python中将func定义为一个Overriding Descriptor,在其__get__方法中构造一个instancemethod对象,并根据被访问函数被访问的情况设置im_func、im_self和im_class成员。在instancemethod实例被调用时,则根据im_func和im_self来完成真正的函数调用。演示这一过程的代码如下:
Class instancemethod(object):
def __call__(self,*args):
if self.im_self == None:
raise 'unbound error'
return self.im_func(self.im_self,*args)
def __init__(self,im_self,im_func,im_class):
self.im_self = im_self
self.im_func = im_func
self.im_class = im_class
class func(object):
...
def __get__(self,owner):
return instancemethod(instance,self,owner)
def __set__(self,value):
pass
...
一个小问题的解决
# coding: utf-8
class A(object):
@property
def _value(self):
# raise AttributeError("test")
return {"v": "This is a test."}
def __getattr__(self,key):
print "__getattr__:",key
return self._value[key]
if __name__ == '__main__':
a = A()
print a.v
运行后可以得到正确的结果 __getattr__: v This is a test. 但是注意,如果把
# raise AttributeError("test")
(编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
