类属性与实例属性

- 日理万妓 2022-04-06 08:26 448阅读 0赞

类属性与实例属性

文章目录

  • 类属性与实例属性
    • 类属性
    • 实例属性
    • 类属性的陷阱
    • 访问属性的优先级顺序
    • 总结
    • 感谢

018.12.5

类属性

同一个类的多个实例共用一个类属性。

  1. import random
  2. class MyClass(object):
  3. # 类属性
  4. num = random.randrange(10000)
  5. if __name__ == "__main__":
  6. myObjOne = MyClass()
  7. myObjTwo = MyClass()
  8. print(myObjOne.num == myObjTwo.num)

可以看到输出结果恒为True。

实例属性

同一个类的多个实例拥有各自的实例属性。

  1. import random
  2. class MyClass(object):
  3. def __init__(self):
  4. self.num = random.randrange(10000)
  5. if __name__ == "__main__":
  6. myObjOne = MyClass()
  7. myObjTwo = MyClass()
  8. print(myObjOne.num == myObjTwo.num)

大概率下,输出结果为False。

类属性的陷阱

当我们想改变类属性的时候:

  1. class MyClass(object):
  2. num = 512
  3. if __name__ == "__main__":
  4. myObj = MyClass()
  5. print(myObj.num, myObj.__class__.num) # 通过实例访问类属性和类层次访问类属性 输出:512, 512
  6. myObj.num = 1024 # 企图通过实例改变类属性
  7. print(myObj.num, myObj.__class__.num) # 输出:1024, 512

可以看到,尽管解释器没有抛异常,但我们并没有改变类属性的值。同时,我们似乎为实例myObj增加了一个实例属性,它与类属性具有相同的名称——num。到底是不是这样呢?可以打印实例的__dict__属性

  1. print(myObj.__dict__) # 输出:{}
  2. myObj.num = 1024
  3. print(myObj.__dict__) # 输出:{'num': 1024}

也就是说,实例不可以 直接 修改类层次的属性。并且当实例属性与类属性同名时,实例属性会屏蔽掉类属性。

访问属性的优先级顺序

用“屏蔽”一词确实含糊地解释了上述现象,但究其根本是Python中存在默认的一套访问顺序。

dercls = DeriveClass(),也就说dercls是一个实例对象时,dercls.num会去类层次找num或者基类中找num:

  1. class BaseClass(object):
  2. num = 512
  3. class DeriveClass(BaseClass):
  4. pass
  5. if __name__ == "__main__":
  6. dercls = DeriveClass()
  7. print(dercls.num) # 输出:512
  8. class BaseClass(object):
  9. num = 512
  10. class DeriveClass(BaseClass):
  11. num = 1024
  12. if __name__ == "__main__":
  13. dercls = DeriveClass()
  14. print(dercls.num) # 输出: 1024

可见实例对象类的优先级 > 基类

  1. class BaseClass(object):
  2. num = 512
  3. class DeriveClass(BaseClass):
  4. num = 1024
  5. def __init__(self):
  6. # 实例属性也同名
  7. self.num = 2048
  8. if __name__ == "__main__":
  9. dercls = DeriveClass()
  10. print(dercls.num) # 输出: 2048

所以实例属性优先级 > 实例该类的类属性

而这个查找属性的过程是在__getattribute__中完成的,当__getattribute__找不到需要的属性时,会抛出AttributeError,此时解释器自动调用__getattr__,仍然找不到,则抛异常AttribueError并且挂掉程序

  1. class BaseClass(object):
  2. def __getattribute__(self, item):
  3. if item == "num":
  4. return 512
  5. def __getattr__(self, item):
  6. if item == "num":
  7. return 1024
  8. if __name__ == "__main__":
  9. basecls = BaseClass()
  10. print(basecls.num) # 输出: 512
  11. class BaseClass(object):
  12. def __getattribute__(self, item):
  13. raise AttributeError
  14. def __getattr__(self, item):
  15. if item == "num":
  16. return 1024
  17. if __name__ == "__main__":
  18. basecls = BaseClass()
  19. print(basecls.num) # 输出: 1024

得到:通过__getattribute__查找属性的优先级 > 通过__getattr__

但通过__getattribute__查找属性并不只有类属性和实例属性,事实上查找描述符也是在__getattribute__中完成的。此时分资料描述符非资料描述符

资料描述符:

  1. class IntegerField(object):
  2. def __init__(self, value):
  3. self.value = value
  4. def __get__(self, instance, owner):
  5. return self.value
  6. def __set__(self, instance, value):
  7. pass
  8. class BaseClass(object):
  9. num = IntegerField(2048) # 这是一个资料描述符
  10. def __init__(self):
  11. self.num = 512
  12. def __getattr__(self, item):
  13. if item == "num":
  14. return 1024
  15. if __name__ == "__main__":
  16. basecls = BaseClass()
  17. print(basecls.num) # 输出:2048

非资料描述符:

  1. class IntegerField(object):
  2. def __init__(self, value):
  3. self.value = value
  4. def __get__(self, instance, owner):
  5. return self.value
  6. class BaseClass(object):
  7. num = IntegerField(2048) # 这是一个非资料描述符
  8. def __init__(self):
  9. self.num = 512
  10. def __getattr__(self, item):
  11. if item == "num":
  12. return 1024
  13. if __name__ == "__main__":
  14. basecls = BaseClass()
  15. print(basecls.num) # 输出: 512

(关于描述符,你可以参看我的属性描述符)

总结

如果user是某个类的实例,那么user.age(以及等价的getattr(user, ‘age’))首先调用__getattribute__。如果类定义了__getattr__方法。那么在__getattribute__抛出AttributeError的时候就会调用__getattr__

描述符的调用(__get__)发生在__getattribute__内部。user = User(),那么user.age顺序如下:

  • 如果age出现在User或其基类的__dict__中,且age是data descriptor,那么调用__get__
  • 如果age出现在obj的__dict__中,那么直接返回obj.__dict__["age"]
  • 如果age出现在User或其基类的__dict__中:

    • . 如果age是non-data descriptor,那么调用其__get__方法;否则返回__dict__["age"]
    • . 如果User有__getattr__方法,在__getattribute__AttributeError之后调用其__getattr__;否则抛出AttributeError并挂掉程序

感谢

  • 参考慕课Bobby老师课程Python高级编程和异步IO并发编程

发表评论

表情:
评论列表 (有 0 条评论,448人围观)

还没有评论,来说两句吧...

相关阅读

    相关 python属性实例属性

    1. 类属性与实例属性基本认识 实例属性是一个类的实例所特有的,这意味着对于两个不同的实例,实例属性经常是不同的。 类属性是类所特有的,所有实例共享这个类属性,类属性经