关于描述符(描述器)的理解
先看段程序
class Role: name = 'lxj' def run(self): print("running") r1 = Role() #在面向对象基础中我们已经知道__dict__是以字典的形式返回类或类实例的所有成员 print(Role.__dict__,r1.__dict__) 结果:{'__module__': '__main__', '__doc__': None, 'run': <function Role.run at 0x00260198>, '__weakref__': <attribute '__weakref__' of 'Role' objects>, '__dict__': <attribute '__dict__' of 'Role' objects>, 'name': 'lxj'} {}
接着来了解下访问属性时的查找策略:比如当我们访问r1.name实际上先在自己(实例)的__dict__字典中找有没有name的key,如果没有在类中的__dict__字典找有没有name的key,如果没有并且类有继承则继续往上找
class Role: name = 'lxj' def run(self): print("running") r1 = Role() #我们先来看下属性的访问 print(Role.name,Role.__dict__['name']) #结果:lxj lxj #方法访问 print(Role.run,Role.__dict__['run']) #结果:<function Role.run at 0x00620198> <function Role.run at 0x00620198> #结果也都是一样的,那想表达什么呢?其实在python2中Role.run是一个unbound method,看了很多资料都没了解python3中为什么不显示unbound方法了,希望有人指导 #继续往下看 print(r1.run) #结果:<bound method Role.run of <__main__.Role object at 0x0025D530>>
总结:按照上面的查找策略,r1.run和Role.run应该是同一个方法才对,为什么会变成unbound method(暂且这么理解)和bound method
可以理解为通过实例访问方法(r1.run),会得到bound method,通过类访问(Role.run)方法访问会得到unbound method
为什么会有这个情况呢,因为descriptor(描述器)
什么是descriptor?
descriptor是对象的一个属性,只不过它存在于类的__dict__中并且有特殊方法__get__(可能还有__set__和__delete)而具有一点特别的功能,为了方便指代这样的属性,我们给它起了个名字叫descriptor属性
descriptor特性
descriptor必须依附对象,作为对象的一个属性,它不能单独存在。还有一点,descriptor必须存在于类的__dict__中
只有在类的__dict__中找到属性,Python才会去看看它有没有__get__等方法,对一个在实例的__dict__中找到的属性,Python根本不理会它有没有__get__等方法,直接返回属性本身
看代码演示
class T(): d = Descriptor() #类中找到属性 # def __init__(self): # self.d = Descriptor() t = T() print(t.d) 结果: ('get', <__main__.Descriptor object at 0x00246BF0>, <__main__.T object at 0x00246C70>, <class '__main__.T'>) class T(): # d = Descriptor() def __init__(self): #实例中找到属性 self.d = Descriptor() t = T() print(t.d) 结果: <__main__.Descriptor object at 0x005F6C70> 没有调用__get__,验证了之前的论证
__get__中参数的意义
class Descriptor(object): def __get__(self, instance, owner): return 'get',self,instance,owner class T(): d = Descriptor() # def __init__(self): # self.d = Descriptor() t = T() print(t.d) #等于d,__get__(t,T) print(T.d) #等于d.__get__(None,T) 结果: ('get', <__main__.Descriptor object at 0x005D6BF0>, <__main__.T object at 0x005D6C70>, <class '__main__.T'>) ('get', <__main__.Descriptor object at 0x005D6BF0>, None, <class '__main__.T'>)
self即当前Descriptor的实例,这里即d,instance就是拥有它的对象,这里即t,owner是instance的类型,即T
接下来说下__get__,__getattribute__,__getattr__区别
__get__、__getattr__、__getattribute都是访问属性(这里我理解的是包括属性和方法)的方法
引子
假设我们有个类A,其中a是A的实例
a.x时发生了什么?属性的lookup顺序如下:
- 如果重载了__getattribute__,则调用.
- a.__dict__, 实例中是不允许有descriptor的,所以不会遇到descriptor
- A.__dict__, 也即a.__class__.__dict__ .如果遇到了descriptor,优先调用descriptor.
- 沿着继承链搜索父类.搜索a.__class__.__bases__中的所有__dict__. 如果有多重继承且是菱形继承的情况,按MRO(Method Resolution Order)顺序搜索.
class C(object): def __setattr__(self, name, value): print("__setattr__ called:", name, value) object.__setattr__(self, name, value) def __getattr__(self, name): print("__getattr__ called:", name) def __getattribute__(self, name): print("__getattribute__ called:", name) return object.__getattribute__(self, name) def func(self): print("aa") c = C() c.x = "foo" print(c.__dict__) print(c.x) 结果: __setattr__ called: x foo __getattribute__ called: __dict__ {'x': 'foo'} __getattribute__ called: x foo
深入
1.object.__getattr__(self, name)
当一般位置找不到attribute的时候,会调用getattr,返回一个值或AttributeError异常。
2.object.__getattribute__(self, name)
无条件被调用,通过实例访问属性。如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常)
3.object.__get__(self, instance, owner)
如果class定义了它,则这个class就可以称为descriptor。owner是所有者的类,instance是访问descriptor的实例,如果不是通过实例访问,而是通过类访问的话,instance则为None。(descriptor的实例自己访问自己是不会触发__get__,而会触发__call__,只有descriptor作为其它类的属性才有意义。)(所以下文的d是作为C2的一个属性被调用)
class C(object): a = 'abc' def __getattribute__(self, *args, **kwargs): print("__getattribute__() is called") return object.__getattribute__(self, *args, **kwargs) def __getattr__(self, name): print("__getattr__() is called ") return name + " from getattr" def __get__(self, instance, owner): print("__get__() is called", instance, owner) return self def foo(self, x): print(x) class C2(object): d = C() if __name__ == '__main__': c = C() c2 = C2() print(c.a) print(c.zzzzzzzz) c2.d print(c2.d.a) 结果: __getattribute__() is called abc __getattribute__() is called __getattr__() is called zzzzzzzz from getattr __get__() is called <__main__.C2 object at 0x0032DB30> <class '__main__.C2'> __get__() is called <__main__.C2 object at 0x0032DB30> <class '__main__.C2'> __getattribute__() is called abc