面向对象知识点补充

关于描述符(描述器)的理解

先看段程序

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顺序如下:

  1. 如果重载了__getattribute__,则调用.
  2. a.__dict__, 实例中是不允许有descriptor的,所以不会遇到descriptor
  3. A.__dict__, 也即a.__class__.__dict__ .如果遇到了descriptor,优先调用descriptor.
  4. 沿着继承链搜索父类.搜索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

 

posted @ 2017-07-30 15:04  zj-luxj  阅读(153)  评论(0编辑  收藏  举报