类属性
同一个类的多个实例共用一个类属性。
import random
class MyClass(object):
# 类属性
num = random.randrange(10000)
if __name__ == "__main__":
myObjOne = MyClass()
myObjTwo = MyClass()
print(myObjOne.num == myObjTwo.num)
可以看到输出结果恒为True。
实例属性
同一个类的多个实例拥有各自的实例属性。
import random
class MyClass(object):
def __init__(self):
self.num = random.randrange(10000)
if __name__ == "__main__":
myObjOne = MyClass()
myObjTwo = MyClass()
print(myObjOne.num == myObjTwo.num)
大概率下,输出结果为False。
类属性的陷阱
当我们想改变类属性的时候:
class MyClass(object):
num = 512
if __name__ == "__main__":
myObj = MyClass()
print(myObj.num, myObj.__class__.num) # 通过实例访问类属性和类层次访问类属性 输出:512, 512
myObj.num = 1024 # 企图通过实例改变类属性
print(myObj.num, myObj.__class__.num) # 输出:1024, 512
可以看到,尽管解释器没有抛异常,但我们并没有改变类属性的值。同时,我们似乎为实例myObj增加了一个实例属性,它与类属性具有相同的名称——num。到底是不是这样呢?可以打印实例的__dict__
属性
print(myObj.__dict__) # 输出:{}
myObj.num = 1024
print(myObj.__dict__) # 输出:{'num': 1024}
也就是说,实例不可以 直接 修改类层次的属性。并且当实例属性与类属性同名时,实例属性会屏蔽掉类属性。
访问属性的优先级顺序
用“屏蔽”一词确实含糊地解释了上述现象,但究其根本是Python中存在默认的一套访问顺序。
当dercls = DeriveClass()
,也就说dercls是一个实例对象时,dercls.num
会去类层次找num或者基类中找num:
class BaseClass(object):
num = 512
class DeriveClass(BaseClass):
pass
if __name__ == "__main__":
dercls = DeriveClass()
print(dercls.num) # 输出:512
class BaseClass(object):
num = 512
class DeriveClass(BaseClass):
num = 1024
if __name__ == "__main__":
dercls = DeriveClass()
print(dercls.num) # 输出: 1024
可见实例对象类的优先级 > 基类。
class BaseClass(object):
num = 512
class DeriveClass(BaseClass):
num = 1024
def __init__(self):
# 实例属性也同名
self.num = 2048
if __name__ == "__main__":
dercls = DeriveClass()
print(dercls.num) # 输出: 2048
所以实例属性优先级 > 实例该类的类属性。
而这个查找属性的过程是在__getattribute__
中完成的,当__getattribute__
找不到需要的属性时,会抛出AttributeError,此时解释器会自动调用__getattr__
,仍然找不到,则抛异常AttribueError并且挂掉程序。
class BaseClass(object):
def __getattribute__(self, item):
if item == "num":
return 512
def __getattr__(self, item):
if item == "num":
return 1024
if __name__ == "__main__":
basecls = BaseClass()
print(basecls.num) # 输出: 512
class BaseClass(object):
def __getattribute__(self, item):
raise AttributeError
def __getattr__(self, item):
if item == "num":
return 1024
if __name__ == "__main__":
basecls = BaseClass()
print(basecls.num) # 输出: 1024
得到:通过\_getattribute__查找属性的优先级 > 通过\_getattr。
但通过__getattribute__
查找属性并不只有类属性和实例属性,事实上查找描述符也是在__getattribute__
中完成的。此时分资料描述符和非资料描述符:
资料描述符:
class IntegerField(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
pass
class BaseClass(object):
num = IntegerField(2048) # 这是一个资料描述符
def __init__(self):
self.num = 512
def __getattr__(self, item):
if item == "num":
return 1024
if __name__ == "__main__":
basecls = BaseClass()
print(basecls.num) # 输出:2048
非资料描述符:
class IntegerField(object):
def __init__(self, value):
self.value = value
def __get__(self, instance, owner):
return self.value
class BaseClass(object):
num = IntegerField(2048) # 这是一个非资料描述符
def __init__(self):
self.num = 512
def __getattr__(self, item):
if item == "num":
return 1024
if __name__ == "__main__":
basecls = BaseClass()
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并发编程
还不快抢沙发