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