python中描述符,也属于比较底层的一个特性。 查看了很多资料终于知道它是干啥的了。
描述符的作用是实现对属性的代理。
如何进行属性验证?
举例来说,如果你想你的类有个属性,age。 对于年龄来说,我们认为它的有效范围应该是0-120。那么怎么限制属性的值必须在这个范围呢?
首先,我们可以在__init__中进行限制,但是这里只是限制了初始化时,age的值必须有效
class Desc(object):
def __init__(self, age):
if 0 <= age <= 120:
self.age = age
else:
self.age = 0
但是上面的实现方式并不完美,因为我们可以通过实例 x.age = 1111 方式给属性赋非法值。
我们继续更新代码如下:
class Desc(object):
def __init__(self, age):
if 0 <= age <= 120:
self._age = age
else:
self._age = 0
def set_age(self,age):
if 0 <= age <= 120:
self._age = age
else:
raise ValueError("invalid age")
def get_age(self):
return self._age
age = property(get_age,set_age)
这次完美了吧,无论初始化还是属性操作都能进行检查了。
但是,这里有个问题,如果我有很多个相似的属性需要检查,譬如一个学生类,它拥有10门成绩,每个成绩都是一个属性。 那么将产生大量的重复性代码。
如果解决这类的问题呢,这里就要用到描述符了。
描述符是什么呢?
描述符(descriptor)实际是一种类,但是这个类实现了__set__ __get__ __delete__ 这3个方法中一个或多个。 其中get是必须的,其他2个可选。
如果实现中实现了__set__ __get__那么称之为数据描述符
如果只实现了 __get__ 那么称之为非数据描述符
怎么应用描述符呢?
要想使用描述符,要将一个描述符类的实例赋值给一个托管类(或叫用户类)的一个类属性, 此后无论是通过托管类的类还是实例来访问这个属性时,都会自动调用描述符对象中的3个函数。
下面的列子中,DataDesc是个描述符,托管类时TestDesc,需要托管的参数包括:name,age,sex,需要进行检查的是,赋值给属性时,要使用规定的类型,譬如age必须是整形。
# -*- coding:utf8 -*-
from weakref import WeakKeyDictionary
# 数据描述符
class DataDesc(object):
def __init__(self, propertyName, propertyType):
"""
:param propertyName: 要代理的属性的名字
:param propertyType: 要代理的属性的类型
"""
self._name = propertyName
self._type = propertyType
# 我们可以把属性存储在这里,也可存储在instance.__dict__中
self._data = WeakKeyDictionary()
def __get__(self, instance, owner):
# __get__ 要么返回属性值,要么抛出异常
print "__get__ called, return the value: %s" % str(self._data[instance])
return self._data[instance]
def __set__(self, instance, value):
# 我们可以在这里对value的类型和范围仅限限定,以此实现对属性的控制
if isinstance(value, self._type):
print "__set__ called, got a new value: %s" % str(value)
self._data[instance] = value
else:
raise ValueError("The value is not the %s" % type(self._type))
def __delete__(self, instance):
del self._data[instance]
class TestDesc(object):
# 必须在类属性中使用描述符,否则不会调用描述符类的__set__, __get__, __delete__
name = DataDesc("name", str)
age = DataDesc("age", int)
sex = DataDesc("gender", bool)
def __init__(self, name, age, sex):
# 这里的赋值操作已经在调用描述符的set函数了
self.name = name
self.age = age
self.sex = sex
td1 = TestDesc("liming", 18, True)
td2 = TestDesc("dasha", 22, False)
print td1.name
print td1.age
print td1.sex
print td2.name
print td2.age
print td2.sex
运行上面的代码,它的输出:
__set__ called, got a new value: liming
__set__ called, got a new value: 18
__set__ called, got a new value: True
__set__ called, got a new value: dasha
__set__ called, got a new value: 22
__set__ called, got a new value: False
__get__ called, return the value: liming
liming
__get__ called, return the value: 18
18
__get__ called, return the value: True
True
__get__ called, return the value: dasha
dasha
__get__ called, return the value: 22
22
__get__ called, return the value: False
False
数据描述符和非数据描述符有什么不同吗?
1. 没有set函数,无法写属性
2. 属性查找属性的不同:操作一个属性时,先要验证这个属性是不是这个实例的类的数据描述符。 如果不是,那么检查属性是否能在实例的__dict__中找到。 如果找不到,那们将会检查这个属性是不是这个实例的类的非数据描述符