一. 预备知识
1) 类中实现了__get__(), __set__(), __delete__()中任意一个方法,就是一个属性描述符
2) 数据属性描述符: 至少实现__set__()和__get__()方法
3) 非数据属性描述符: 实现除__set__()以外的方法
4) 使用属性描述符后, 可以在为属性赋值时增加一些逻辑判断
5) 假设obj是实例化对象, 那么 obj.name 等价于 getattr(obj, "name")
二. 属性的查找过程分析
2.1 __getattribute__方法是访问属性的入口
直接说结论:
属性查找优先级: __getattribute__方法 > 实例属性
__getattribute__方法是所有属性调用的入口, 需要注意的是这个方法如果使用不当,会导致程序死循环
# -*- coding: utf-8 -*- class Person(object): def __init__(self): self.name = "chinablue" self.age = 18 def __getattribute__(self, item): print(f"调用了__getattribute__方法") if "age" == item: return 38 return object.__getattribute__(self, item) p = Person() print(p.__dict__) # 实例属性信息: {‘name‘: ‘chinablue‘} print(p.name) # 实例中有name属性: chinablue print(p.age) # 实例中有age属性,但__getattribute__方法拦截并修改了age属性: 38 # print(p.job) # 如果实例对象访问的属性不存在,也会访问__getattribute__方法
2.2 类属性对属性查找的影响
直接说结论:
属性查找优先级: 实例属性 > 类属性
实例没有的属性,会去类属性中查找
# -*- coding: utf-8 -*- class Person(object): # age是类属性 age = 18 def __init__(self): # name是实例属性 self.name = "chinablue" p = Person() print(p.__dict__) # 实例属性信息: {‘name‘: ‘chinablue‘} print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")}) # 类的属性信息: {‘age‘: 18} print(p.name) # 实例中有name属性: chinablue print(p.age) # 实例中没有age属性,类中有age属性: 18
2.3 __getattr__方法对属性查找的影响
直接说结论:
属性查找优先级: 实例属性 > 类属性 > __getattr__方法
如果实例访问的属性不存在,则会抛出AttributeError异常
如果实例中定义了__getattr__方法, 当实例访问的属性不存在时, 会去访问__getattr__方法
# -*- coding: utf-8 -*- class Person(object): def __init__(self): # name是实例属性 self.name = "chinablue" # 当实例对象找不到属性时,会来调用__getattr__方法 def __getattr__(self, item): return "djtest" p = Person() print(p.__dict__) # 实例属性信息: {‘name‘: ‘chinablue‘} print(p.name) # 实例中有name属性: chinablue print(p.job) # 实例中没有job属性,会调用__getattr__函数: djtest
另外, 如果实例对象中同时存在__getattr__方法和__getattribute__方法, 代码如下:
# -*- coding: utf-8 -*- class Person(object): def __init__(self): ... def __getattribute__(self, item): print(f"调用了__getattribute__方法") return object.__getattribute__(self, item) def __getattr__(self, item): print(f"调用了__getattr__方法") raise AttributeError(f"{item}属性不存在") p = Person() print(p.name) # 实例中没有name属性,先调用__getattribute__方法, 再调用__getattr__方法
2.4 非数据属性描述符对属性查找的影响
直接说结论:
属性查找优先级: 实例属性 > 非数据属性描述符 > __getattr__方法
非数据属性描述符的优先级与类属性相当
# -*- coding: utf-8 -*- class PersonName: def __get__(self, instance, owner): return "djtest" class PersonAge: def __get__(self, instance, owner): return 28 class Person(object): # age和name是非数据描述符 age = PersonAge() name = PersonName() def __init__(self): self.name = "chinablue" p = Person() print(p.__dict__) # 实例属性信息: {‘name‘: ‘chinablue‘} print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")}) # 类的属性信息: {‘age‘: <PersonAge object>, ‘name‘: <PersonName object>} print(p.name) # 实例中有name属性: chinablue print(p.age) # 实例中没有age属性,类中(非数据描述符中)有age属性: 28
2.5 数据属性描述符对属性查找的影响
直接说结论:
属性查找优先级: 数据属性描述符 > 实例属性
# -*- coding: utf-8 -*- class PersonName: def __get__(self, instance, owner): return "djtest" def __set__(self, instance, value): self.name = value class Person(object): # name是数据描述符 name = PersonName() def __init__(self): self.name = "chinablue" p = Person() print(p.__dict__) # 实例属性信息: {} print({k: v for k, v in Person.__dict__.items() if not k.startswith("__")}) # 类的属性信息: {‘name‘: <__main__.PersonName object at 0x033709B0>} print(p.name) # 实例中有name属性: djtest
三. 本章小结
对象属性的查找优先级参考如下:
__getattribute__方法 > 数据属性描述符 > 实例属性 > 类属性 | 非数据属性描述符 > __getattr__方法