属性描述符
引子
在学django,flask的时候,需要定义模型类,也就是数据库的时候,我有些疑惑,为什么要这么写?
我拿出一个普通的项目内的模型类,大概是这个样子
from django.db import models
class BaseModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
ordering = ['-created_at']
这里暂时先可以这么理解,这个定义的类映射到数据库的表,类的属性映射到数据库的字段。
我们定义一个user用户类,并实例化一个user对象
class User:
def __init__(self, name, phone):
self.name = name
self.phone = phone
user = User('davecurry', 1231231321)
如果我们想在实例化这个对象的时候,规范大家,在传入name的值时让必须输入字符串类型,传入Phone的值时必须输入int类型,不然无法实例化该怎么做呢?当然我们用 @property 静态属性和 @属性.setter 可以完成这样的操作,但是如果user内的属性越来越多时,这样的做法会显得麻烦,且代码量大,这时候我们需要一个更简便的方法——属性描述符
属性描述符
当一个类,实现了’____get(),__set(),__delete(),三个方法其中之一,它便被称作属性描述符。我们定义两个属性描述符,一个限制字符串,一个限制整型。在User类中让属性描述符限制类中的属性。
class CharField:
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value, str):
raise ValueError('string needed')
self.value = value
def __delete__(self, instance):
pass
class IntField:
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('integer needed')
class User:
name = CharField()
phone = IntField()
if __name__ == '__main__':
user = User()
user.name = 'Davie' # 调用了set()方法
print(user.name) # 调用了get()放方法
结果
Davie
如果赋值成整型
if __name__ == '__main__':
user = User()
user.name = 123
print(user.name)
结果报错
ValueError: string needed
在set方法上如果传入参数类型不正确,我写了主动抛出异常的代码,所以导致报错。
实际上属性描述符分为数据描述符和非数据描述符,而且它们对于属性的查找其实是不一样的,如果我们调用user.name时,它究竟是怎么个查找顺序呢?
如果user是某个类的实例,那么user.name和它等价有着同种效果的getattr(user,‘name’)会先调用__getattribute__,如果类中定义了__getattr__方法,那么在__getattribute__抛出AttributeError的时候就会调用到__getattr__。
而对于描述符__get__的调用,则是发生在__getattribute__内部的。
具体顺序是(以user.name为例):
(1)如果“age”是出现在User或其基类的__dict__中, 且name是data descriptor(属性描述符), 那么调用其__get__方法, 否则
(2)如果“name”出现在user的__dict__中, 那么直接返回 obj.__dict__[‘name’], 否则
(3)如果"name”出现在User或其基类的__dict__中,且不是属性描述符
(3.1)如果age是non-data descriptor,那么调用其__get__方法, 否则
(3.2)返回 __dict__['age']
(4)如果User有__getattr__方法,调用__getattr__方法,否则
(5)抛出AttributeError