2022年 11月 4日

Python属性拦截器之__getattribute__与__getattr__

在Python中自定义了很多内建属性,如__getattribute__(),即属性访问拦截器,它的作用是当我们访问实例对象的属性时,并不会立即返回属性的值,而是自动调用__getattribute__()方法,并将它的返回结果作为属性值。(注意是实例对象属性,类属性访问不会调用__getattribute__方法,而是直接由类名.__dict__[属性名]获取)

值得一提的在Python中对实例对象属性的访问有如下顺序:

__getattribute__ –> 数据描述符–> 实例属性 –> 类属性,非数据描述器 –> __getattr__

  1. (1)判断实例对象所属(attr)的类是否重写了__getattribute__(),如果定义了则调用,将调用结果作为属性值返回
  2. (2)若是未显示定义__getattribute__(),则在对象的__dict__中查找属性(若该属性是python自动产生的则直接返回)
  3. (3)若不是由python直接产生的,则看其是否是描述符属性,若是描述符属性(且为数据描述符)则由描述符的__get__方法的返回值作为属性值,若是非数据描述符
  4. 则看当前对象的__dict__中是否有和父类类属性中描述符属性同名属性,若当前实例对象中有则直接返回该值,若没有,则由父类描述符的__get__方法返回
  5. 值作为属性值;
  6. (4)若是在自身的__dict__中未找到该属性,则到父类中查找,并重复2,3步骤,仍未找到则触发__getattr__抛出AttributeError

从中从上不难看出__getattribute__ 和__getattr__方法的重要性。

实现__getattribute__方法与__getattr__:

  1. class A:
  2. demo = 'PYTHON'
  3. def __init__(self, des, title):
  4. self.des = des
  5. self.title = title
  6. def __getattribute__(self, item):
  7. """重写__getattribute__方法,实现拦截功能"""
  8. if item == 'des':
  9. return "you can't acess this "
  10. else:
  11. return 'no such attr'
  12. a = A('python __getattribute__', 'python')
  13. print(a.des) # 访问实例属性des
  14. >> you can't access this
  15. print(a.demo) # 通过实例对象访问类属性demo
  16. >> no such attr
  17. print(a.xxx) # 访问不存在的属性
  18. >> no such attr
  19. print(A.demo) # 通过类名直接访问类属性
  20. >> PYTHON

从上不难发现当我们通过实例对象访问属性时,都会自动触发定义的__getattribute__方法对我们的行为进行拦截,而拿不到真正的属性,但是通过类直接访问时却不会触发(此外内置getattr和hasattr也会触发这个方法__getattribute__的调用)。

在重写了__getattribute__()方法之后,我们如何拿到真正想要的属性的值呢?—-只需在__getattribute__中调用超类的__getattribute__方法即可。

  1. class A:
  2. demo = 'PYTHON'
  3. def __init__(self, des, title):
  4. self.des = des
  5. self.title = title
  6. def __getattribute__(self, item):
  7. """重写__getattribute__方法,实现拦截功能"""
  8. if item == 'des':
  9. return "you can't access this "
  10. else:
  11. return object.__getattribute__(self, item)
  12. # 或者super().__getattribute__(item)
  13. # 注意此处不能使用self.item 来获取,否则会无限递归调用,也就是说我们可以直接
  14. # 使用基类的__getattribute__方法获取实例属性的值
  15. # object.__getattribute__(a, 'des') --->python __getattribute__
  16. a = A('python __getattribute__', 'python')
  17. print(a.des) # 访问实例属性des
  18. >> you can't access this
  19. print(a.demo) # 通过实例对象访问类属性demo,获取到了真正的属性
  20. >> PYTHON

从前面可知当访问属性过程中使用__getattribute__方法未找到所需要的属性时会触发__getattr__,但是当我们重写了__getattribute__方法之后,是否还会触发调用?如下:

  1. class A:
  2. demo = 'PYTHON'
  3. def __init__(self, des, title):
  4. self.des = des
  5. self.title = title
  6. def __getattribute__(self, item):
  7. """重写__getattribute__方法,实现拦截功能"""
  8. if item == 'des':
  9. return "you can't access this "
  10. else:
  11. return "_*_*_*_*"
  12. def __getattr__(self, item):
  13. return 'no such attr !!!'
  14. a = A('python __getattribute__', 'python')
  15. print(a.xxx) # 访问不存在的属性
  16. >> _*_*_*_*

不难发现当访问的属性不存在时,并没有执行__getattr__方法,也就是__getattribute__的优先级更高,如果想要__getattr__方法也执行,则需要显示调用它或者在__getattribute__中抛出异常,如下:

  1. class A:
  2. demo = 'PYTHON'
  3. def __init__(self, des, title):
  4. self.des = des
  5. self.title = title
  6. def __getattribute__(self, item):
  7. """重写__getattribute__方法,实现拦截功能"""
  8. if item == 'des':
  9. raise AttributeError
  10. else:
  11. return object.__getattribute__(self, item)
  12. def __getattr__(self, item):
  13. return 'no such attr !!!'
  14. a = A('python __getattribute__', 'python')
  15. print(a.des) # 手动抛出AttributeError时也会触发调用__getattr__方法
  16. >> no such attr !!!
  17. print(a.xxx) # 访问不存在属性时,由object.__getattribute__(self, item)自动抛出异常从而
  18. # 触发调用__getattr__方法
  19. >> no such attr !!!