8.3 属性描述符和属性查找过程

property 实现在数据获取和设置时增加额外逻辑处理,并对外提供简单接口

在批量属性操作,如验证,则需要每个属性都要写一遍,代码重复

  • 数据属性描述符:实现 __get__ 和 __set__ 方法
  • 非数据属性描述符: 实现 __get__ 方法
import numbers

class IntField:
    def __init__(self):
        self._data = None

    def __get__(self, instance, owner):
        print(instance)     # <__main__.User object at 0x000002B88B270288>
        print(owner)        # <class '__main__.User'>
        print(type(instance) is owner)          # True
        print(instance.__class__ is owner)      # True
        return self._data

    def __set__(self, instance, value):
        if not isinstance(value, numbers.Integral):
            raise ValueError('Need int value')
        # 重点来了,如何保存 value 呢,instance or self
        # 如果 instance.attribute 又会触发 __set__ 描述符
        self._data = value

    def __delete__(self, instance):
        pass


class User:
    age = IntField()
    num = IntField()


if __name__ == '__main__':
    user = User()
    user.age = 18
    print(user.__dict__)    # {} "age" 并没有进入到 __dict__

    print(user.age)

转变原先简单的属性获取顺序

user 某个类实例,user.age 等价于 getattr(user, 'age')

首先调用 __getattribute__
    如果定义了 __getattr__ 方法,调用 __getattribute__ 抛出异常 AttributeError 触发__getattr__
    而对于描述符(__get__)的调用,则是发生在 __getattribute__内部

user = User(), 调用 user.age 顺序如下:
(1) 如果 'age' 是出现在 User 或基类的 __dict__ 中,且 age 是data descriptor,那么调用其 __get__(instance, owner) 方法,否则
(2) 如果 'age' 出现在 user 的 __dict__ 中,那么直接返回 user.__dict__['age'],否则
(3) 如果 'age' 出现在 User 或基类的 __dict__ 中
    (3.1) 如果 age 是 non-data descriptor, 那么调用其 __get__ 方法,否则
    (3.2) 返回 User.__dict__['age']
(4) 如果 User 有 __getattr__ 方法,调用 __getattr__ 方法,否则
(5) 抛出异常 AttributeError
  • 属性描述符优先级最高
class NonDataIntFiled:
    def __get__(self, instance, owner):
        print(instance)
        print(owner)
        return 100

class User:
    age = NonDataIntFiled()

if __name__ == '__main__':
    user = User()
    # user.__dict__['age'] = 18
    # user.age = 18
    # print(user.__dict__)
    print(user.age)