python魔法函数中的描述器Descriptor

今天在整理以前代码的时候,发现了一个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作为其它类的属性才有意义。)

文章的原文地址是,http://xiaorui.cc/2015/08/30/python%E9%AD%94%E6%B3%95%E5%87%BD%E6%95%B0%E4%B8%AD%E7%9A%84%E6%8F%8F%E8%BF%B0%E5%99%A8descriptor/

在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__在开发中用的机会不多。 



大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

电子邮件地址不会被公开。 必填项已用*标注