什么是描述符
python是一种动态的编程语言,因此不能像java那样在编译的时候确定对象的类型,如果我们希望能够像java中的那样规定某个实例属性的类型时该怎么做呢?这就需要用到描述符了。
描述符是实现了特定协议的类, 这个协议就是描述符协议。
描述符协议包括以下三种方法:
- get: 用于访问属性
- set: 用于设置属性
- delete: 用于删除
比如,我们有一个类:
1 | class Demo(object): |
如果我们希望name固定为str类型,虽然我们可以通过类似get\set方法的来统一读写name属性,但用户仍然可以通过obj.name等方式来设置。我们可以通过添加一个set方法来控制该属性的类型。
1 | class BeString(object): |
覆盖型描述符合非覆盖型描述符
如上述示例,包含set特殊方法的类称为覆盖型描述符,只包含get特殊方法的类称为非覆盖型描述符。
使用覆盖型描述符会覆盖实例属性的赋值操作:
1 | class BeString(object): |
为了区别显示,我这里把描述符的名称设置为了xname,从d的dict来看,set方法覆盖了实例属性的赋值操作d.name
恢复正常的显示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class BeString(object):
def __init__(self,name):
self.attr_name = name
def __set__(self,instance,value):
if isinstance(value,str):
instance.__dict__[self.attr_name] = value
else:
raise Exception("Must be str.")
def __get__(self,instance,type):
return instance.__dict__[self.attr_name]
class Demo(object):
name = BeString("xname")
d = Demo()
d.name = "张三"
print(vars(d))
print(d.name)
---------
{'xname': '张三'}
张三
如果删掉set方法就成了非覆盖型描述符,如下例:
1 | class BeString(object): |
因为实例属性存在,__get__方法并没有被调用。而family_name不存在示例属性,则调用了描述符的__get__方法显示了”随便起个名”的文字。
在类中覆盖描述符
在类中给类属性赋值都会覆盖掉描述符,这是一种猴子补丁技术,不管是不是覆盖型描述符。
如下面的例子:
1 | class BeString(object): |
可以看到,不论是__set__还是__get__都没有生效,哪怕在后续的操作中继续使用实例方法也不会有效。
1 | d = Demo() |
另外,
1 | d = Demo() |
从这里能看出来,在读取类的属性上,是可以调用类上面的描述符处理,而写却不行。
1 | class BeString(object): |
属性查找的顺序
当使用实例对象访问属性的时候,查找会以下面的顺序进行:
类属性——>覆盖型描述符——>实例属性——>非覆盖型描述符——> __getattr__
__getattr__和__getattribute__
getattr会在getattribute找不到属性的时候被调用,getattribute是属性查找的入口,其内部实现了我们上述的查找顺序。
1 | class Demo(object): |
如果我们自己实现getattribute,则需要手动抛出AttributeError才能调用到getattr.
描述符在很多著名的框架中都有引用,像Django中的ORM,SQLalchemy等,都是描述符应用的范例。