首页 > 技术文章 > Python核心编程-描述符

badboyf 2016-12-06 13:45 原文

python中,什么描述符。描述符就是实现了"__get__”、“__set__”或”__delete__” 方法中至少一个的对象。什么是非数据描述符,就是实现了__get__方法的对象,也就是初始化后,就只能读。数据描述符就是实现了__get__和__set__方法的对象,也就是说这个属性可读可重新赋值。

看一下Python核心编程中描述符的例子:

class DevNull2(object):
    def __get__(self, obj, typ=None):
        print 'Accessing attribute... ignoring'
    def __set__(self, obj, val):
        print 'Attempt to assign %r... ignoring' % (val)

class C2(object):
    foo = DevNull2()
c2 = C2()
c2.foo = 'bar'

这里DevNull2就是一个描述符,还有,下面的c2.foo的赋值并不是将DevNull2这个替换,因为Devnull2是一个描述符,所以,这里相当于代替了foo的属性值,在进行赋值操作的时候,会走DevNull2中的__set__方法。为什么描述符可以代表对象属性也就是为什么访问属性时调用的是__set__,__get__方法也不是访问的属性。这里需要了解属性解析的执行方式,对于对象来说属性解析机制在object.__getattribute__()方法中,因为对每个属性的实例都会调用到这个特殊的方法。这个方法被用来查找类的属性,同时也是你的一个代理,调用它可以进行属性的访问等操作回顾一下上面的原型,如果一个实例调用了__get__()方法,这就可能传入了一个类型或类的对象。举例来说,给定类 X 和实例 x, x.foo 由__getattribute__()转化成:
type(x).__dict__['foo'].__get__(x, type(x))

如果类调用了__get__()方法,那么 None 将作为对象被传入(对于实例, 传入的是 self):
X.__dict__['foo'].__get__(None, X)

优先级的排序:

  • 类属性
  • 数据描述符
  • 实例属性
  • 非数据描述符
  • 默认为__getattr__()

在优先级链中,类字典中发现的数据描述符的优先级高于实例变量,实例变量优先级高于非数据描述符,如果提供了getattr(),优先级链会为getattr()分配最低优先级。对于一个给定的对象类,可以通过自定义__getattribute__方法来重写优先级链。

另外一个例子来说明怎么使用描述符

class TypedProperty(object):  
    def __init__(self, key, typ, val=None):
        print 'key :' , key
        self.key = '_' + key
        print 'typ :' , typ
        self.typ = typ
        print 'val :' , val
        self.val = val if val else typ()
 
    def __get__(self, instance, cls):
        print 'in __get__     ', self.key, self.typ, self.val, instance
        return getattr(instance, self.key, self.val)
   
    def __set__(self, instance, value):
        print 'in __set__       ', self.key, self.typ, self.val, value
        if not isinstance(value, self.typ):
            raise TypeError("Must be a %s" % self.typ)
        setattr(instance, self.key, value)
  
    def __delete__(self, instance):
        print 'in __delete__'
        raise AttributeError("Can't delete attribute")
   
class Foo(object):
    name = TypedProperty("name", str)
    num = TypedProperty("num", int, 42)
foo = Foo()
foo.num = 12
foo.num
结果:

key : name
typ : <type 'str'>
val : None
key : num
typ : <type 'int'>
val : 42
in __set__ _num <type 'int'> 42 12
in __get__ _num <type 'int'> 42 <__main__.Foo object at 0x7f371f94a310>

 

都能看出来,在描述符中的key属性就是属性的名,val是属性值。肯定有有人注意到在__init__()方法中,将key值设成了‘_’ + key,这里并不是一定要写成这样,这里的目的主要就是为了在__set__和__get__方法中的instance也就是Foo的实例中的属性不要与描述符中的key的形式一样,就是如果instance中的属性是num,描述符中的属性就不能写成num,要区分开,这里在前边放了一个‘_’来区分(可以用任何形式来区分,也就是'_'可以随便写成‘whatever’)。如果没有区分开,在调用foo.num = 12 的时候,走进__set__方法中,在setattr()方法中又调用了instantce中的key属性也就是又调用了foo.num,最终形成一个循环。导致maximum recursion depth exceeded while getting the str of an object

python提供更简介的方式(利用property()内建函数):

property(fget=None, fset=None, fdel=None, doc=None)

class HideX(object):
    def __init__(self, x):
        self.x = x
    def get_x(self):
        return ~self.__x
    def set_x(self, x):
        assert isinstance(x, int), '"x" must be an integer!'
        self.__x = ~x
    x = property(get_x, set_x)
inst = HideX(20)
print inst.x
inst.x = 30
print inst.x

也可以用set_x和get_x方法设置和获取属性。如果利用property()方法感觉太乱,也可以用@property

class HideX(object):
    def __init__(self, x):
        self.x = x
        @property
        def x():
            def fget(self):
                return ~self.__x
            def fset(self, x):
                assert isinstance(x, int), '"x" must be an integer!'
                self.__x = ~x
            return locals()

这时也就只能用inst.x来获取或者重新赋值了。

还可以利用下面的方法:

class C(object):
    def __init__(self):
        self._x = None
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value
    @x.deleter
    def x(self):
        del self._x

 

推荐阅读