关于描述符(描述器)的理解
先看段程序
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