python描述符descriptor(一)

时间:2022-01-21 13:42:12

Python 描述符是一种创建托管属性的方法。每当一个属性被查询时,一个动作就会发生。这个动作默认是get,set或者delete。不过,有时候某个应用可能会有

更多的需求,需要你设计一些更复杂的动作。最好的解决方案就是编写一个执行符合需求的动作的函数,然后指定它在属性被访问时运行。一个具有这种功能的对象

称为描述符。描述符是python方法,绑定方法,super,property,staticmethod和classmethod的实现基础。

1.描述符协议

描述符descriptor就是一个表示属性值的对象,通过实现一个或多个__get__,__set__,__delete__方法,可以将描述符与属性访问机制挂钩,还可以自定义这些操作。

__get__(self,instance,own):用于访问属性,返回属性的值。instance为使用描述符的实例对象,own为实例所属的类。当通过类访问属性时,instance为None。

__set__(self,instance,value):设定属性值。

__delete__(self,instance):删除属性值。

2.描述符如何实现

class Descriptor(object):
def __get__(self, instance, owner):
print 'getting:%s'%self._name
return self._name
def __set__(self, instance, name):
print 'setting:%s'%name
self._name = name
def __delete__(self, instance):
print 'deleting:%s'%self._name
del self._name
class Person(object):
name = Descriptor()

一个很简单的描述符对象就产生了,现在可以对一个Person对象进行属性name的读取,设置和删除:

>>> p=Person()
>>> p.name='john'
setting:john
>>> p.name
getting:john
'john'
>>> del p.name
deleting:john

注意:描述符只能在类级别上进行实例化,不能通过在__init__()和其他方法中创建描述符对象来为每个实例创建描述符。

具有描述符的类使用的属性名称比实例上存储的属性名称具有更高的优先级。为了能让描述符在实例上存储值,描述符必须挑选一个与它本身所用名称不同的名称。

如上例,Person类初始化__init__函数为实例设置属性就不能用name名称了。

data描述符与none-data描述符:

如果实现了__get__和__set__就是一个data描述符,如果只有__get__就是一个non-data描述符。不同的效果在于data描述符总是替代在一个实例中的属性实现,

而non-data描述符由于没有set,在通过实例对属性赋值时,例如上面的p.name = 'hello',不会再调用__set__方法,会直接把实例属性p.name设为'hello'。

当然如果仅仅在__set__中raise AttributeError,仍然得到的是一个non-data的描述符。

描述符调用机制:

当查询一个对象的属性a.attr时,如果python发现attr是个描述符对象,如何读取属性取决于对象a:

直接调用:最简单的调用是直接使用代码调用描述符的方法,attr.__get__(a)

实例绑定:如果a是个实例对象,调用方法:type(a).__dict__['attr'].__get__(a,type(a))

类绑定:如果A是个类对象,调用方法:A.__dict__['attr'].__get__(None,A)

super绑定:如果a是个super实例,那么super(B,obj).m()通过查询obj.__class__.__mro__找到B的基类A,然后执行A.__dict__['m'].__get__(obj,obj.__class__)

3.执行属性类型检查的描述符

class TypedProperty(object):
def __init__(self,name,attr_type,default=None):
self.name='_'+name
self.type=attr_type
self.default=default if default else attr_type()
def __get__(self,instance,own):
return getattr(instance,self.name,self.default)
def __set__(self,instance,value):
if not isinstance(value,self.type):
raise TypeError,'Must be %s'%self.type
setattr(instance,self.name,value)
def __delete__(self,instance):
raise AttributeError('Can not delete attribute')
class Foo(object):
name=TypedProperty('name',str)
num=TypedProperty('num',int,37)

上述描述符可以对属性的类型进行检查,如果name属性不设为str类型或者num不设为int类型,就会报错:

>>> f.name=21
TypeError: Must be <type 'str'>

而且禁止对属性进行删除操作:

>>> del f.name
AttributeError: Can not delete attribute

f.name 隐形的调用type(f).__dict__['name'].__get__(f,Foo),即Foo.name.__get__(f,Foo)。

上述描述符实际是存储在实例上的,name通过setattr(f,_name,value)存储在f._name上,num存储在f._num上,这也是加下划线的原因,

否则描述符名称name会和实例属性name发生冲突,描述符属性f.name会覆盖掉实例属性f.name。