今天在整理以前代码的时候,发现了一个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__在开发中用的机会不多。
