今天在整理以前代码的时候,发现了一个tornade写的rpc服务,看到里面用了很多的描述器,说实话,3年前的代码咋实现的,我居然也我忘了。。。。看来有必要从头再整理下了。 python的魔法函数。
长话短说,什么是描述器? 当一个物件拥有__get__()方法(必要),以及选择性的__set__()、__delete__()方法时,它可以作为描述器(Descriptor):
def __get__(self, instance, owner) def __set__(self, instance, value) def __delete__(self, instance)
v = t.a <----> v = __get__(a, t) t.a = v <-----> __set__(a, t, v) del t.a <-----> __delete__(a, t)
object.__get__(self, instance, owner)
如果class定义了它,则这个class就可以称为描述器( descriptor )。owner是所有者的类,instance是访问descriptor的实例,如果不是通过实例访问,而是通过类访问的话,instance则为None。(descriptor的实例自己访问自己是不会触发__get__,而会触发__call__,只有descriptor作为其它类的属性才有意义。)
在Python语言中,当描述器实际是某个类别的特性成员时,对于类别特性的取得、设定或删除,将会交由描述器来决定如何处理(除了那些内建特性,如__class__等特性之外)。例如:
class Descriptor: def __get__(self, instance, owner): print(self, instance, owner) def __set__(self, instance, value): print(self, instance, value) def __delete__(self, instance): print(self, instance) class Some: x = Descriptor()
我们按照上面的例子,走一遍 !
s = Some() s.x s.x = 10 del s.x
以上的用法是用描述器实现的get set del,他其实相当于下面操作过程:
s = Some()
Some.__dict__[‘x’].__get__(s, Some);
Some.__dict__[‘x’].__set__(s, 10);
Some.__dict__[‘x’].__delete__(s);
继续,如果这样做的话:
Some.x
相当于:
Some.__dict__[‘x’].__get__(None, Some)
需要说明的是,在class中我们会经常使用@property属性化用法,他的原理也是描述器.
class Property(object): def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): print obj print self.fget if obj is None: return self if self.fget is None: raise AttributeError, "unreadable attribute" return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, "can't set attribute" self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "can't delete attribute" self.fdel(obj)
上面更多是描述器属性方面的介绍,下面我再举例说明一个描述器调用函数的方法。
class ProxyDescriptor(object): def __init__(self, name): self._attr_name = '%s_proxy' % name def __get__(self, instance, owner): return getattr(instance, self._attr_name) def __set__(self, instance, value): proxy = getattr(instance, self._attr_name) proxy._proxied = proxy._shortcut(value) class test(): k = ProxyDescriptor('query') b = ProxyDescriptor('query') def __init__(self,args): pass def query_proxy(self,*args): print args return args def get_args(self): return 123 t = test('query') t.k('nima')
在 特性名称空间 中谈过特性搜寻的顺序,依其中描述整理一下的话,特性的寻找顺序是:
在实例的__dict__中寻找是否有相符的特性名称
在产生实例的类别__dict__中寻找是否有相符的特性名称.
如果实例有定义__getattr__(),则看__getattr__()如何处理
如果实例没有定义__getattr__(),则丢出AttributeError
如果加上描述器,则寻找的顺序是:
在产生实例的类别__dict__中寻找是否有相符的特性名称。如果找到 且实际是个描述器实例(也就是具有__get__()方法),且具有__set__()或__delete__()方法,若为取值,则传回__get__ ()方法的值,若为设值,则呼叫__set__()(没有这个方法则丢出AttributeError),若为删除特性,则呼叫__delete__()(没有这个方法则丢出AttributeError),如果描述器仅具有__get__(),则先进行第2步
有点绕口,这样吧,咱们直接来个小例子,看看他的顺序,及文中魔法函数的使用方法。
#coding:utf-8 class xiaorui(object): a = 'abc' def __getattribute__(self, *args, **kwargs): print("__getattribute__() is called") return object.__getattribute__(self, *args, **kwargs) def get(self): print 'get() is called ' def __getattr__(self, name): print name print("__getattr__() is called ") return lambda: self.get() def __get__(self, instance, owner): print("__get__() is called", instance, owner) return self def foo(self, x): print(x) class C2(object): d = xiaorui() class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25 if __name__ == '__main__': s = Student() print s.age() c = xiaorui() c2 = C2() c.get() print c.testget() """ 当实例调用的时候,触发__getattribute__""" print c.a """ 当没有nima的时候,触发__getattr__""" print c.nima """当通过描述符访问C1类的时候,触发__get__ """ print c2.d print(c2.d.a)
下面是直接的结果…..
[ruifengyun@devops ~ ]python bb.py
__getattribute__() is called
abc
__getattribute__() is called
nima
__getattr__() is called
nima from getattr
('__get__() is called', <__main__.C2 object at 0x10f47d090>, <class '__main__.C2'>)
<__main__.C object at 0x10f452a10>
('__get__() is called', <__main__.C2 object at 0x10f47d090>, <class '__main__.C2'>)
__getattribute__() is called
abc
[ruifengyun@devops ~ ]echo xiaorui.cc
可以看出,每次通过实例访问属性,都会经过__getattribute__函数。而当属性不存在时,仍然需要访问__getattribute__,不过接着要访问__getattr__。这就好像是一个异常处理函数(__getattr__ 只有在找不到他要的属性和函数时,才会触发,你在这里可以做异常处理)。 每次访问descriptor(即实现了__get__的类),都会先经过__get__函数。
有什么地方不懂,大家可以拿上面的代码来调试下,看看描述器是怎么工作的。 另外我自己觉得魔法函数中的__get__, __set__ , __delete__在开发中用的机会不多。