zoukankan      html  css  js  c++  java
  • 描述器 descriptors

    描述器的表现

    用到3个魔术方法:__get__(),__set__(),__delete__(),用到这三个方法其一的类就是描述器。

    方法签名如下:

    object.__get__(self,instance,owner),self是实例本身,instance是owner的实例。

    object.__set__(self,instance,value)

    object.__delete__(self,instance)

    self指代当前实例,调用者。

    instance是owner的实例,owner是属性的所属的类。

    请思考下面程序的执行流程是什么?

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            
            
    print("-"*20)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x.a1)
    
    结果为:
    a.init
    --------------------
    a1
    ====================
    b.init
    a1

    可以看出执行的先后顺序吧?

    类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印a.init。然后执行到打印B.x.a1。

    然后实例化并初始化B的实例b.。

    打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            self.x = 100
            
            
    print(B.x.a1)
    
    b = B()
    print(B.x.a1)
    print(b.x.a1)
    
    结果为:
    a.init
    a1
    b.init
    a1
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-1-5cbf3a145f86> in <module>
         15 b = B()
         16 print(B.x.a1)
    ---> 17 print(b.x.a1)
    
    AttributeError: 'int' object has no attribute 'a1'
    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            self.x = 100
            
            
    print(B.x.a1)
    
    结果为:
    a.init
    <__main__.A object at 0x03A554B0> None <class '__main__.B'>
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-6-049b3adf350f> in <module>
         14 
         15 
    ---> 16 print(B.x.a1)
    
    AttributeError: 'NoneType' object has no attribute 'a1'

    上面这样再访问,直接报错了。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            self.x = 100
            
            
    #print(B.x.a1)
    print(B.x)
    
    结果为:
    a.init
    <__main__.A object at 0x03D56430> None <class '__main__.B'>
    None

    上面加了__get__()方法,行为发生了变化,变成了不能直接再访问了。访问被__get__方法拦截了。上例中的两个None不是同一个东西。上面的none是instance,后面的none是get的返回值。

    看懂执行流程了,再看下面的程序,对类A做一些改造。如果在类A中实现了__get__方法。看看变化。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            
            
    print("-"*20)
    print(B.x)
    #print(B.x.a1)#抛异常,attributeerror:"nonetype" object has no attribute "a1"
    
    print("="*20)
    b = B()
    print(b.x)
    #print(b.x.a1)#抛异常,attributeerror:"nonetype" object has no attribute "a1"
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005BA9D30>None<class '__main__.B'>
    None
    ====================
    B.init
    A.__get__<__main__.A object at 0x0000000005BA9D30><__main__.B object at 0x0000000005BA9C88><class '__main__.B'>
    None

    因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用__get__方法。

    如何解决上例中访问报错的问题,问题应该来自于__get__方法。

    self,instance,owner这三个参数是什么意思?

    <__main__.A object at 0x0000000005BA9D30>None,
    <__main__.A object at 0x0000000005BA9D30><__main__.B object at 0x0000000005BA9C88>
    self都是A的实例,owner都是B类。
    instance说明
    none表示是没有B类的实例,对应调用B.x

    <__main__.B object at 0x0000000005BA9C88>表示是B的实例,对应调用B().x

    使用返回值解决,返回self就是A的实例,该实例有a1属性,返回正常。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    print(b.x.a1)
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005BA9F28>None<class '__main__.B'>
    <__main__.A object at 0x0000000005BA9F28>
    A.__get__<__main__.A object at 0x0000000005BA9F28>None<class '__main__.B'>
    a1
    ====================
    B.init
    A.__get__<__main__.A object at 0x0000000005BA9F28><__main__.B object at 0x0000000005BA9F60><class '__main__.B'>
    <__main__.A object at 0x0000000005BA9F28>
    A.__get__<__main__.A object at 0x0000000005BA9F28><__main__.B object at 0x0000000005BA9F60><class '__main__.B'>
    a1

    那么类B的实例属性也可以?

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            self.b = A()#实例属性也指向一个A的实例
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    print(b.x.a1)
    print(b.b)#并没有触发__get__ 结果为: A.init
    -------------------- A.__get__<__main__.A object at 0x0000000005BDC4A8>None<class '__main__.B'> <__main__.A object at 0x0000000005BDC4A8> A.__get__<__main__.A object at 0x0000000005BDC4A8>None<class '__main__.B'> a1 ==================== B.init A.init A.__get__<__main__.A object at 0x0000000005BDC4A8><__main__.B object at 0x0000000005BDC518><class '__main__.B'> <__main__.A object at 0x0000000005BDC4A8> A.__get__<__main__.A object at 0x0000000005BDC4A8><__main__.B object at 0x0000000005BDC518><class '__main__.B'> a1

    <__main__.A object at 0x0000000005BDCAC8>
     

    从运行结果可以看出,只有类属性是类的实例才行。

    所以,当一个类的类属性等于另一个类的实例的时候,且这个类实现了上面三个方法中的一个,它就是描述器的类。而且通过类来访问这个属性,它就会触发这个方法。而如果是通过实例来访问这个属性的话,它不会触发这个方法。

    描述器定义

    Python中,一个类实现了__get__,__set__,__delete__三个方法中的任何一个方法,就是描述器。

    如果仅仅实现了__get__,就是非数据描述器(non -data descriptor)

    同时实现了__get__,__set__就是数据描述器(data descriptor)

    如果一个类的类属性设置为描述器,那么它称为owner属主。

    属性的访问顺序

    为上例中的类B增加实例属性x

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            self.x = "b.x"#增加实例属性x 
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    print(b.x.a1)#attributeerror:"str" object has no attribute "a1"
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005BDCD30>None<class '__main__.B'>
    <__main__.A object at 0x0000000005BDCD30>
    A.__get__<__main__.A object at 0x0000000005BDCD30>None<class '__main__.B'>
    a1
    ====================
    B.init
    b.x
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-7-eb7fbc87b715> in <module>
         22 b = B()
         23 print(b.x)
    ---> 24 print(b.x.a1)#attributeerror:"str" object has no attribute "a1"
    
    AttributeError: 'str' object has no attribute 'a1'

    b.x访问到了实例的属性,而不是描述器。

    继续修改代码,为类A增加__set__方法。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
        
        def __set__(self,instance,value):
            print("A.__set__{}{}{}".format(self,instance,value))
            self.data = value
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            self.x = "b.x"#增加实例属性x 
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    print(b.x.a1)#返回a1
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005BDDCC0>None<class '__main__.B'>
    <__main__.A object at 0x0000000005BDDCC0>
    A.__get__<__main__.A object at 0x0000000005BDDCC0>None<class '__main__.B'>
    a1
    ====================
    B.init
    A.__set__<__main__.A object at 0x0000000005BDDCC0><__main__.B object at 0x0000000005BDDC18>b.x
    A.__get__<__main__.A object at 0x0000000005BDDCC0><__main__.B object at 0x0000000005BDDC18><class '__main__.B'>
    <__main__.A object at 0x0000000005BDDCC0>
    A.__get__<__main__.A object at 0x0000000005BDDCC0><__main__.B object at 0x0000000005BDDC18><class '__main__.B'>
    a1

    返回变成了a1,访问到了描述器的数据。

    当一个类属性,是一个数据描述器的话,对这个类属性实例的属性操作(跟类属性相同),相当于操作类属性。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print("A.__get__",self,instance,owner)
            return self
        
        def __set__(self,instance,value):
            print("A.__set__",self,instance,value)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            self.x = 100
            
            
    print(B.x)
    print(B.x.a1)
    print()
    
    b = B()
    print(B.x)
    print(b.x.a1)
    
    print(B.__dict__)
    print(b.__dict__)
    
    结果为:
    a.init
    A.__get__ <__main__.A object at 0x03F5B2F0> None <class '__main__.B'>
    <__main__.A object at 0x03F5B2F0>
    A.__get__ <__main__.A object at 0x03F5B2F0> None <class '__main__.B'>
    a1
    
    b.init
    A.__set__ <__main__.A object at 0x03F5B2F0> <__main__.B object at 0x03F5BAF0> 100
    A.__get__ <__main__.A object at 0x03F5B2F0> None <class '__main__.B'>
    <__main__.A object at 0x03F5B2F0>
    A.__get__ <__main__.A object at 0x03F5B2F0> <__main__.B object at 0x03F5BAF0> <class '__main__.B'>
    a1
    {'__module__': '__main__', 'x': <__main__.A object at 0x03F5B2F0>, '__init__': <function B.__init__ at 0x03D50B70>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
    {}
    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print("A.__get__",self,instance,owner)
            return self
        
        def __set__(self,instance,value):
            print("A.__set__",self,instance,value)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            #self.x = 100
            self.x = A()
            
            
    print(B.x)
    print(B.x.a1)
    print()
    
    b = B()
    print(B.x)
    print(b.x.a1)
    
    print(B.__dict__)
    print(b.__dict__)
    
    结果为:
    a.init
    A.__get__ <__main__.A object at 0x03F5BE30> None <class '__main__.B'>
    <__main__.A object at 0x03F5BE30>
    A.__get__ <__main__.A object at 0x03F5BE30> None <class '__main__.B'>
    a1
    
    b.init
    a.init
    A.__set__ <__main__.A object at 0x03F5BE30> <__main__.B object at 0x03F5B5F0> <__main__.A object at 0x03F5B790>
    A.__get__ <__main__.A object at 0x03F5BE30> None <class '__main__.B'>
    <__main__.A object at 0x03F5BE30>
    A.__get__ <__main__.A object at 0x03F5BE30> <__main__.B object at 0x03F5B5F0> <class '__main__.B'>
    a1
    {'__module__': '__main__', 'x': <__main__.A object at 0x03F5BE30>, '__init__': <function B.__init__ at 0x03CC59C0>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
    {}

    属性查找顺序

    实例的__dict__优先于非数据描述器

    数据描述器优先于实例的__dict__

    __delete__方法有同样的效果,有了这个方法,就是数据描述器。

    尝试增加下面的代码。看看字典的变化。

    b.x = 500

    B.x=600

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print("A.__get__",self,instance,owner)
            return self
        
        def __set__(self,instance,value):
            print("A.__set__",self,instance,value)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            #self.x = 100
            self.x = A()
            
            
    print(B.x)
    print(B.x.a1)
    print()
    
    b = B()
    print(B.x)
    print(b.x.a1)
    
    B.x = 500
    print(B.x)
    
    结果为:
    a.init
    A.__get__ <__main__.A object at 0x03F5B810> None <class '__main__.B'>
    <__main__.A object at 0x03F5B810>
    A.__get__ <__main__.A object at 0x03F5B810> None <class '__main__.B'>
    a1
    
    b.init
    a.init
    A.__set__ <__main__.A object at 0x03F5B810> <__main__.B object at 0x03F5B050> <__main__.A object at 0x03F5BC70>
    A.__get__ <__main__.A object at 0x03F5B810> None <class '__main__.B'>
    <__main__.A object at 0x03F5B810>
    A.__get__ <__main__.A object at 0x03F5B810> <__main__.B object at 0x03F5B050> <class '__main__.B'>
    a1
    500
    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print("A.__get__",self,instance,owner)
            return self
        
        def __set__(self,instance,value):
            print("A.__set__",self,instance,value)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            #self.x = 100
            self.x = A()
            
            
    print(B.x)
    print(B.x.a1)
    print()
    
    b = B()
    print(B.x)
    print(b.x.a1)
    
    b.x = 500
    print(b.x)
    
    结果为:
    a.init
    A.__get__ <__main__.A object at 0x03F5B030> None <class '__main__.B'>
    <__main__.A object at 0x03F5B030>
    A.__get__ <__main__.A object at 0x03F5B030> None <class '__main__.B'>
    a1
    
    b.init
    a.init
    A.__set__ <__main__.A object at 0x03F5B030> <__main__.B object at 0x03F5BAD0> <__main__.A object at 0x03F5B3D0>
    A.__get__ <__main__.A object at 0x03F5B030> None <class '__main__.B'>
    <__main__.A object at 0x03F5B030>
    A.__get__ <__main__.A object at 0x03F5B030> <__main__.B object at 0x03F5BAD0> <class '__main__.B'>
    a1
    A.__set__ <__main__.A object at 0x03F5B030> <__main__.B object at 0x03F5BAD0> 500
    A.__get__ <__main__.A object at 0x03F5B030> <__main__.B object at 0x03F5BAD0> <class '__main__.B'>
    <__main__.A object at 0x03F5B030>

    b.x= 500.这是调用数据描述器的__set__方法,或调用非数据描述器的实例覆盖。

    B.x = 600,赋值即定义,这是覆盖类属性。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("a.init")
            
        def __get__(self,instance,owner):
            print("A.__get__",self,instance,owner)
            return self
        
        def __set__(self,instance,value):
            print("A.__set__",self,instance,value)
            
    class B:
        x = A()
        def __init__(self):
            print("b.init")
            #self.x = 100
            #self.x = A()
            
    
    b = B()
    
    b.x = 500
    print(b.x)
    
    结果为:
    a.init
    b.init
    A.__set__ <__main__.A object at 0x03F5B370> <__main__.B object at 0x03F5B830> 500
    A.__get__ <__main__.A object at 0x03F5B370> <__main__.B object at 0x03F5B830> <class '__main__.B'>
    <__main__.A object at 0x03F5B370>

    本质(进阶)

    Python真的会做的这么复杂吗?再来一套属性查找顺序规则?看看非数据描述器和数据描述器,类B及其__dict__的变化。

    屏蔽和不屏蔽__set__方法,看看变化。

    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
        
        #def __set__(self,instance,value):
            #print("A.__set__{}{}{}".format(self,instance,value))
            #self.data = value
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            self.x = "b.x"#增加实例属性x 
            self.y = "b.y"
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    #print(b.x.a1)#返回a1
    print(b.y)
    print("字典")
    print(b.__dict__)
    print(B.__dict__)
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005BA9DA0>None<class '__main__.B'>
    <__main__.A object at 0x0000000005BA9DA0>
    A.__get__<__main__.A object at 0x0000000005BA9DA0>None<class '__main__.B'>
    a1
    ====================
    B.init
    b.x
    b.y
    字典
    {'x': 'b.x', 'y': 'b.y'}
    {'__module__': '__main__', 'x': <__main__.A object at 0x0000000005BA9DA0>, '__init__': <function B.__init__ at 0x0000000005BEE378>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
    class A:
        def __init__(self):
            self.a1 = "a1"
            print("A.init")
            
        def __get__(self,instance,owner):
            print("A.__get__{}{}{}".format(self,instance,owner))
            return self#解决返回None的问题
        
        def __set__(self,instance,value):
            print("A.__set__{}{}{}".format(self,instance,value))
            self.data = value
            
    class B:
        x = A()
        def __init__(self):
            print("B.init")
            self.x = "b.x"#增加实例属性x 
            self.y = "b.y"
            
            
    print("-"*20)
    print(B.x)
    print(B.x.a1)
    
    print("="*20)
    b = B()
    print(b.x)
    #print(b.x.a1)#返回a1
    print(b.y)
    print("字典")
    print(b.__dict__)
    print(B.__dict__)
    
    结果为:
    A.init
    --------------------
    A.__get__<__main__.A object at 0x0000000005D92E48>None<class '__main__.B'>
    <__main__.A object at 0x0000000005D92E48>
    A.__get__<__main__.A object at 0x0000000005D92E48>None<class '__main__.B'>
    a1
    ====================
    B.init
    A.__set__<__main__.A object at 0x0000000005D92E48><__main__.B object at 0x0000000005D92F28>b.x
    A.__get__<__main__.A object at 0x0000000005D92E48><__main__.B object at 0x0000000005D92F28><class '__main__.B'>
    <__main__.A object at 0x0000000005D92E48>
    b.y
    字典
    {'y': 'b.y'}
    {'__module__': '__main__', 'x': <__main__.A object at 0x0000000005D92E48>, '__init__': <function B.__init__ at 0x0000000005BEE598>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}

    原来不是什么数据描述器优先级高,而是把实例的属性从__dict__中给去除掉了,造成了该属性如果是数据描述器优先访问的假象。

    说到底,属性访问顺序从来就没有变过。

    Python中的描述器

    描述器在Python中应用非常广泛。

    Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。

    property()函数实现为一个数据描述器,因此,实例不能覆盖属性的行为。

    class A:
        @classmethod
        def foo(cls):#非数据描述器
            pass
        @staticmethod#非数据描述器
        def bar():
            pass
        
        @property#数据描述器
        def z(self):
            return 5
        
        def getfoo(self):#非数据描述器
            return self.foo
        
        def __init__(self):#非数据描述器
            self.foo = 100
            self.bar = 200
            #self.z = 300
            
    a = A()
    print(a.__dict__)
    print(A.__dict__)
    
    结果为:
    {'foo': 100, 'bar': 200}
    {'__module__': '__main__', 'foo': <classmethod object at 0x0000000005BDD780>, 'bar': <staticmethod object at 0x0000000005BDDC18>, 'z': <property object at 0x0000000005BF47C8>, 'getfoo': <function A.getfoo at 0x0000000005BCE730>, '__init__': <function A.__init__ at 0x0000000005BCEBF8>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

    foo、bar都可以在实例中覆盖,但是Z不可以。

    练习

    1.实现staticmethod装饰器,完成staticmethod装饰器的功能

    class StaticMethod:
        def __init__(self,fn):
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            
    class A:
        @StaticMethod
        def foo():#foo = StaticMethod(fn)
            print("staic")
            
    f = A.foo
    print(f)
    
    结果为:
    <__main__.StaticMethod object at 0x00000000059E0DA0> None <class '__main__.A'>
    None

    上面的例子就触发了描述器。现在需要f加一个括号就能用,这个时候get的返回值就需要是self.fn。

    class StaticMethod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return self.fn
            
    class A:
        @StaticMethod
        def foo():#foo = StaticMethod(fn)
            print("staic")
            
    f = A.foo
    print(f)
    
    结果为:
    <function A.foo at 0x0000000005A6D6A8>
    <__main__.StaticMethod object at 0x0000000005A4F5F8> None <class '__main__.A'>
    <function A.foo at 0x0000000005A6D6A8>

    可以看到返回的fn和前面初始化打印的fn是同一个。所以就可以调用了。

    2.实现classmethod装饰器,完成classmethod装饰器的功能。

    class ClassMthod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return self.fn
    
    class A:
        @ClassMthod
        def bar(cls):
            print(cls.__name__)
            
    f = A.bar
    print(f)
    f()
    
    结果为:
    <function A.bar at 0x00000000059D41E0>
    <__main__.ClassMthod object at 0x0000000005A79748> None <class '__main__.A'>
    <function A.bar at 0x00000000059D41E0>
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-9-f50fce04a0fc> in <module>
         15 f = A.bar
         16 print(f)
    ---> 17 f()
    
    TypeError: bar() missing 1 required positional argument: 'cls'

    上面这样调用,显示少了一个参数。改成下面这样。

    class ClassMthod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return self.fn
    
    class A:
        @ClassMthod
        def bar(cls):
            print(cls.__name__)
            
    f = A.bar
    print(f)
    f(A)#等价于A.bar(A)
    
    结果为:
    <function A.bar at 0x0000000005A6DE18>
    <__main__.ClassMthod object at 0x0000000005A79C18> None <class '__main__.A'>
    <function A.bar at 0x0000000005A6DE18>
    A

    这样是对的,但是这样不符合我们常规的调用。我们习惯于A.bar().这个时候在get的返回值改成下面这样。

    class ClassMthod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            return self.fn(owner)
    
    class A:
        @ClassMthod
        def bar(cls):
            print(cls.__name__)
            
    f = A.bar
    print(f)
    f()
    
    结果为:
    <function A.bar at 0x0000000005A7B840>
    <__main__.ClassMthod object at 0x0000000005A827F0> None <class '__main__.A'>
    A
    None
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-12-2da94a88aead> in <module>
         15 f = A.bar
         16 print(f)
    ---> 17 f()
    
    TypeError: 'NoneType' object is not callable

    可是为什么还是出错?上面这样写虽然打印出了结果,但是get的return是个函数,调用了bar函数,但是bar函数的返回值是个None。所以打印F是none,而none是不能调用的。所以f()调用会出错。改成下面这样。

    from functools import partial
    
    class ClassMthod:
        def __init__(self,fn):
            print(fn)
            self.fn = fn
            
        def __get__(self,instance,owner):
            print(self,instance,owner)
            #return self.fn(owner)
            return partial(self.fn,owner)
    
    class A:
        @ClassMthod
        def bar(cls):
            print(cls.__name__)
            
    f = A.bar
    print(f)
    f()
    
    结果为:
    <function A.bar at 0x0000000005A7B2F0>
    <__main__.ClassMthod object at 0x0000000005A82D30> None <class '__main__.A'>
    functools.partial(<function A.bar at 0x0000000005A7B2F0>, <class '__main__.A'>)
    A
    #类staticmethod装饰器
    
    class StaticMethod():#怕冲突改名字
        def __init__(self,fn):
            self._fn = fn 
            
        def __get__(self,instance,owner):
            return self._fn
        
    class A:
        @StaticMethod
        #stmtd = StaticMethod(stmtd)
        def stmtd():
            print("static method")
            
    A.stmtd()
    A().stmtd()
    
    结果为:
    static method
    static method
    from functools import partial
    
    #类staticmethod装饰器
    
    class ClassMthod():#怕冲突改名字
        def __init__(self,fn):
            self._fn = fn 
            
        def __get__(self,instance,owner):
            ret = self._fn(owner)
            return ret
        
    class A:
        @ClassMthod
        #stmtd = ClassMthod(stmtd)
        #调用A。clsmtd()或者A().clsmtd()
        def clsmtd(cls):
            print(cls.__name__)
            
            
    print(A.__dict__)
    A.clsmtd
    A.clsmtd()
    
    结果为:
    {'__module__': '__main__', 'stmtd': <__main__.ClassMthod object at 0x0000000005BF3C88>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
    A
    A
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-19-c4c59c53e47c> in <module>
         21 print(A.__dict__)
         22 A.stmtd
    ---> 23 A.stmtd()
    
    TypeError: 'NoneType' object is not callable

    A.clsmtd()的意识就是None(),一定会报错,怎么修改?

    A.clsmtd()其实就应该是A.clsmtd(cls)(),应该怎么处理?

    A.clsmeth = A.clsmtd(cls)

    应该用partial函数

    from functools import partial
    
    #类classmethod装饰器
    
    class ClassMthod():#怕冲突改名字
        def __init__(self,fn):
            self._fn = fn 
            
        def __get__(self,instance,cls):
            ret = partial(self._fn,cls)
            return ret
        
    class A:
        @ClassMthod
        #stmtd = ClassMthod(stmtd)
        #调用A.clsmtd()或者A().clsmtd()
        def clsmtd(cls):
            print(cls.__name__)
            
            
    print(A.__dict__)
    print(A.clsmtd)
    A.clsmtd()
    
    结果为:
    {'__module__': '__main__', 'clsmtd': <__main__.ClassMthod object at 0x0000000005BFCBE0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
    functools.partial(<function A.clsmtd at 0x0000000005BF7620>, <class '__main__.A'>)
    A

    3.对实例的数据进行验证

    class Person():
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age

    对上面的类的实例的属性name、age进行数据校验。

    思路

    1. 写函数,在__init__中先检查,如果不合格,直接抛异常。
    2. 装饰器,使用inspect模块完成
    3. 描述器
    #写函数检查
    
    class Person():
        def __init__(self,name:str,age:int):
            params = ((name,str),(age,int))
            if not self.checkdata(params):
                raise TypeError()
            self.name = name 
            self.age = age
            
        def checkdata(self,params):
            for p,t in params:
                if not isinstance(p,t):
                    return False
            return True 
    p = Person("tom",20)
    p = Person("tom","20")
    
    结果为:
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-35-76490ffbbcc3> in <module>
         15         return True
         16 p = Person("tom",20)
    ---> 17 p = Person("tom","20")
    
    <ipython-input-35-76490ffbbcc3> in __init__(self, name, age)
          5         params = ((name,str),(age,int))
          6         if not self.checkdata(params):
    ----> 7             raise TypeError()
          8         self.name = name
          9         self.age = age
    
    TypeError: 

    这种方法耦合度太高。

    装饰器的方式,前面写过类似的,这里不再赘述。

    描述器方式

    需要使用数据描述器,写入实例属性的时候做检查。

    先把架子搭上。

    class Typed():
        def __init__(self):
            pass
        
        def __get__(self,instance,owner):
            pass
        
        def __set__(self,instance,value):
            print("T.set",self,instance,value)
            
    class Person():
        name = Typed()
        age = Typed()
        
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
    p1 = Person("tom",21)
    
    结果为:
    T.set <__main__.Typed object at 0x0000000005A4F400> <__main__.Person object at 0x0000000005A4FB00> tom
    T.set <__main__.Typed object at 0x0000000005A4FB38> <__main__.Person object at 0x0000000005A4FB00> 21

    然后 修改上面的代码。

    class Typed():
        def __init__(self,type):
            self.type = type
        
        def __get__(self,instance,owner):
            pass
        
        def __set__(self,instance,value):
            print("T.set",self,instance,value)
            if not isinstance(value,self.type):
                raise ValueError(value)
            
    class Person():
        name = Typed(str)
        age = Typed(int)
        
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
    p1 = Person("tom","21")
    
    结果为:
    T.set <__main__.Typed object at 0x0000000005A8EB70> <__main__.Person object at 0x0000000005A8E9E8> tom
    T.set <__main__.Typed object at 0x0000000005A8EBA8> <__main__.Person object at 0x0000000005A8E9E8> 21
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-23-610d56b34b36> in <module>
         19         self.age = age
         20 
    ---> 21 p1 = Person("tom","21")
    
    <ipython-input-23-610d56b34b36> in __init__(self, name, age)
         17     def __init__(self,name,age):
         18         self.name = name
    ---> 19         self.age = age
         20 
         21 p1 = Person("tom","21")
    
    <ipython-input-23-610d56b34b36> in __set__(self, instance, value)
          9         print("T.set",self,instance,value)
         10         if not isinstance(value,self.type):
    ---> 11             raise ValueError(value)
         12 
         13 class Person():
    
    ValueError: 21

    这样就可以了。年龄输入正确的话不会报错。

    class Typed:
        def __init__(self,name,type):
            self.name = name
            self.type = type
            
        def __get__(self,instance,owner):
            if instance is not None:
                return instance.__dict__[self.name]
            return self
        
        def __set__(self,instance,value):
            if not isinstance(value,self.type):
                raise TypeError(value)
            instance.__dict__[self.name] = value
            
    class Person():
        name = Typed("name",str)#不优雅
        age = Typed("age",int)#不优雅
        
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
            
    p = Person("tom","20")
            
    
    结果为:
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-39-6987db3956e3> in <module>
         22         self.age = age
         23 
    ---> 24 p = Person("tom","20")
         25 
    
    <ipython-input-39-6987db3956e3> in __init__(self, name, age)
         20     def __init__(self,name:str,age:int):
         21         self.name = name
    ---> 22         self.age = age
         23 
         24 p = Person("tom","20")
    
    <ipython-input-39-6987db3956e3> in __set__(self, instance, value)
         11     def __set__(self,instance,value):
         12         if not isinstance(value,self.type):
    ---> 13             raise TypeError(value)
         14         instance.__dict__[self.name] = value
         15 
    
    TypeError: 20

    代码看似不错,但是有硬编码,能否直接获取形参类型,使用inspect模块。

    先做个实验

    params = inspect.signature(Person).parameters

    看看返回什么结果

    完整代码如下:

    class Typed:
        def __init__(self,name,type):
            self.name = name
            self.type = type
            
        def __get__(self,instance,owner):
            if instance is not None:
                return instance.__dict__[self.name]
            return self
        
        def __set__(self,instance,value):
            if not isinstance(value,self.type):
                raise TypeError(value)
            instance.__dict__[self.name] = value
    
    import inspect
    def typeassert(cls):
        params = inspect.signature(cls).parameters
        print(params)
        for name,param in params.items():
            print(param.name,param.annotation)
            if param.annotation !=param.empty:#注入类属性
                setattr(cls,name,Typed(name,param.annotation))
        return cls
    
    @typeassert
    class Person():
        #name = Typed("name",str)#装饰器注入
        #age = Typed("age",int)
        
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
            
        def __repr__(self):
            return "{} is {}".format(self.name,self.age)
            
    p = Person("tom","20")
    p = Person("tom",20)
    print(p)
    
    结果为:
    OrderedDict([('name', <Parameter "name: str">), ('age', <Parameter "age: int">)])
    name <class 'str'>
    age <class 'int'>
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-46-95dc606a99c5> in <module>
         36         return "{} is {}".format(self.name,self.age)
         37 
    ---> 38 p = Person("tom","20")
         39 p = Person("tom",20)
         40 print(p)
    
    <ipython-input-46-95dc606a99c5> in __init__(self, name, age)
         31     def __init__(self,name:str,age:int):
         32         self.name = name
    ---> 33         self.age = age
         34 
         35     def __repr__(self):
    
    <ipython-input-46-95dc606a99c5> in __set__(self, instance, value)
         11     def __set__(self,instance,value):
         12         if not isinstance(value,self.type):
    ---> 13             raise TypeError(value)
         14         instance.__dict__[self.name] = value
         15 
    
    TypeError: 20

    可以把上面的函数装饰器改成类装饰器,如何写?

    class Typed:
        def __init__(self,type):
            self.type = type
            
        def __get__(self,instance,owner):
            pass
        
        def __set__(self,instance,value):
            print("T.set",self,instance,value)
            if not isinstance(value,self.type):
                raise ValueError(value)
    
    import inspect
    class TypeAssert():
        def __init__(self,cls):
            self.cls = cls#记录着被包装的Person类
            params = inspect.signature(self.cls).parameters
            print(params)
            for name,param in params.items():
                print(name,param.annotation)
                if param.annotation !=param.empty:#注入类属性
                    setattr(self.cls,name,Typed(param.annotation))
            print(self.cls.__dict__)
            
        def __call__(self,name,age):
            p = self.cls(name,age)#重新构建一个新的Person对象
            return p
    
    
    @TypeAssert
    class Person():#Person = TypeAssert(Person)
        #name = Typed("name",str)#装饰器注入
        #age = Typed("age",int)
        
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
            
            
    p1 = Person("tom",18)
    print(id(p1))
    p2 = Person("tom",20)
    print(id(p2))
    p3 = p2 = Person("tom","20")
    
    结果为:
    OrderedDict([('name', <Parameter "name: str">), ('age', <Parameter "age: int">)])
    name <class 'str'>
    age <class 'int'>
    {'__module__': '__main__', '__init__': <function Person.__init__ at 0x0000000005BEEF28>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x0000000005C0C2E8>, 'age': <__main__.Typed object at 0x0000000005C0C550>}
    T.set <__main__.Typed object at 0x0000000005C0C2E8> <__main__.Person object at 0x0000000005C0C400> tom
    T.set <__main__.Typed object at 0x0000000005C0C550> <__main__.Person object at 0x0000000005C0C400> 18
    96519168
    T.set <__main__.Typed object at 0x0000000005C0C2E8> <__main__.Person object at 0x0000000005BDC438> tom
    T.set <__main__.Typed object at 0x0000000005C0C550> <__main__.Person object at 0x0000000005BDC438> 20
    96322616
    T.set <__main__.Typed object at 0x0000000005C0C2E8> <__main__.Person object at 0x0000000005C0CA58> tom
    T.set <__main__.Typed object at 0x0000000005C0C550> <__main__.Person object at 0x0000000005C0CA58> 20
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    <ipython-input-53-87224824ed3f> in <module>
         42 p2 = Person("tom",20)
         43 print(id(p2))
    ---> 44 p3 = p2 = Person("tom","20")
    
    <ipython-input-53-87224824ed3f> in __call__(self, name, age)
         24 
         25     def __call__(self,name,age):
    ---> 26         p = self.cls(name,age)#重新构建一个新的Person对象
         27         return p
         28 
    
    <ipython-input-53-87224824ed3f> in __init__(self, name, age)
         35     def __init__(self,name:str,age:int):
         36         self.name = name
    ---> 37         self.age = age
         38 
         39 
    
    <ipython-input-53-87224824ed3f> in __set__(self, instance, value)
          9         print("T.set",self,instance,value)
         10         if not isinstance(value,self.type):
    ---> 11             raise ValueError(value)
         12 
         13 import inspect
    
    ValueError: 20

     作业

    将前面的链表,封装成容器

    要求

    1. 提供__getitem__,__iter__方法,__setitem__方法
    2. 使用一个列表,辅助完成上面的方法
    3. 进阶:不使用列表,完成上面的方法
    class SingleNode:#结点保存内容和下一跳
        def __init__(self,item,prev = None,next = None):
            self.item = item
            self.next = next
            self.prev = prev#增加上一跳
            
        def __repr__(self):
            #return repr(self.item)
            return "({} <=={} ==>{})".format(
            self.prev.item if self.prev else None,
            self.item,
            self.next.item if self.next else None)
        
            
    class LinkedList():
        def __init__(self):
            self.head = None
            self.tail = None #思考tail的属性
            self.size = 0 #以后实现
            self.items = []
            
        def append(self,item):
            node = SingleNode(item)
            if self.head is None:
                self.head = node #设置开头结点,以后不变
            else:
                self.tail.next = node #当前最后一个结点关联下一跳
                node.prev = self.tail #前后关联
            self.tail = node #更新结尾结点
            
            self.items.append(node)
            return self
        
        def insert(self,index,item):
            if index<0:#不接受负数
                raise IndexError("Not negative index {}".format(index))
                
            current = None
            for i,node in enumerate(self.iternodes()):
                if i ==index:#找到了
                    current = node
                    break
            else: #没有break,尾部追加
                self.append(item)
                return 
            
            #break,找到了
            node = SingleNode(item)
            prev = current.prev
            next = current
            
            if prev is None:#首部
                self.head = node
            else:#不是首元素
                prev.next = node
                node.prev = prev
            node.next = next
            next.prev = node
            
            self.items.insert(index,node)
            
        def pop(self):
            if self.tail is None:#
                raise Exception("empty")
                    
            node = self.tail
            item = node.item
            prev = node.prev
            if prev is None:#only one node
                self.head = None
                self.tail = None
            else:
                prev.next = None
                self.tail = prev
                
            self.items.pop()  
            return item
            
        def remove(self,index):
            if self.tail is None:#
                raise Exception("empty")
                
            if index <0:#不接受负数
                raise IndexError("not negative index {}".format(index))
                
            current = None
            for i,node in enumerate(self.iternodes()):
                if i == index:
                    current = node
                    break
                    
            else:#not found
                raise IndexError("wrong index {}".format(index))
                
            prev = current.prev
            next = current.next
            
            #4种情况
            if prev is None and next is None:#only one node
                self.head = None
                self.tail = None
                
            elif prev is None:#头部
                self.head = next
                next.prev = None
                
            elif next is None:#尾部
                self.tail = prev
                prev.next = None
                
            else:#在中间
                prev.next = next
                next.prev = prev
                
            del current          
            self.items.pop(index)   
        
        def iternodes(self,reverse = False):
            current = self.tail if reverse else self.head
            while current:
                yield current
                current = current.prev if reverse else current.next
                
                
        def __len__(self):
            return len(self,items)
        
        def __getitem__(self,index):
            return self.items[index]
        
        def __setitem__(self,index,value):
            node = self[index]
            node.item = value
        
        def __iter__(self):
            return self.iternodes()
    class SingleNode:#结点保存内容和下一跳
        def __init__(self,item,prev = None,next = None):
            self.item = item
            self.next = next
            self.prev = prev#增加上一跳
            
        def __repr__(self):
            #return repr(self.item)
            return "({} <=={} ==>{})".format(
            self.prev.item if self.prev else None,
            self.item,
            self.next.item if self.next else None)
        
            
    class LinkedList():
        def __init__(self):
            self.head = None
            self.tail = None #思考tail的属性
            self.size = 0 #以后实现
            
        def __len__(self):
            return self.size
            
        def append(self,item):
            node = SingleNode(item)
            if self.head is None:
                self.head = node #设置开头结点,以后不变
            else:
                self.tail.next = node #当前最后一个结点关联下一跳
                node.prev = self.tail #前后关联
            self.tail = node #更新结尾结点
            self.size+=1
            return self
        
        def insert(self,index,item):
            if index<0:#不接受负数
                raise IndexError("Not negative index {}".format(index))
                
            current = None
            for i,node in enumerate(self.iternodes()):
                if i ==index:#找到了
                    current = node
                    break
            else: #没有break,尾部追加
                self.append(item)
                return 
            
            #break,找到了
            node = SingleNode(item)
            prev = current.prev
            next = current
            
            if prev is None:#首部
                self.head = node
            else:#不是首元素
                prev.next = node
                node.prev = prev
            node.next = next
            next.prev = node
            self.size+=1
            
        def pop(self):
            if self.tail is None:#
                raise Exception("empty")
                    
            node = self.tail
            item = node.item
            prev = node.prev
            if prev is None:#only one node
                self.head = None
                self.tail = None
            else:
                prev.next = None
                self.tail = prev
            self.size-=1
            return item
            
        def remove(self,index):
            if self.tail is None:#
                raise Exception("empty")
                
            if index <0:#不接受负数
                raise IndexError("not negative index {}".format(index))
                
            current = None
            for i,node in enumerate(self.iternodes()):
                if i == index:
                    current = node
                    break
                    
            else:#not found
                raise IndexError("wrong index {}".format(index))
                
            prev = current.prev
            next = current.next
            
            #4种情况
            if prev is None and next is None:#only one node
                self.head = None
                self.tail = None
                
            elif prev is None:#头部
                self.head = next
                next.prev = None
                
            elif next is None:#尾部
                self.tail = prev
                prev.next = None
                
            else:#在中间
                prev.next = next
                next.prev = prev
                
            del current
            self.size-=1
                      
                
        
        def iternodes(self,reverse = False):
            current = self.tail if reverse else self.head
            while current:
                yield current
                current = current.prev if reverse else current.next
                
                
         #可以这样写,__iter__ = iternodes,,但是这里有参数,这个时候可以使用偏函数。类似于__repr__=__str__       
        def __iter__(self):
            pass
        
        def __getitem__(self,index):
            #>0
            for i,node in enumerate(self.iternodes()):
                if i ==index:
                    return node
            
            #<0
            for i,node in enumerate(self.iternodes(True),1):
                if -i ==index:#或者abs(index)
                    return node
                
            #for i,node in enumerate(self.iternodes(False if index>=0 else True),0 if index>=0 else 1):
                #if -i ==index:#或者abs(index)
                    #return node 
        
        def __setitem__(self,key,value):
            self[key].item = value
                
                
    ll = LinkedList()
    ll.append("abc")
    ll.append(1)
    ll.append(2)
    ll.append(3)
    ll.append(4)
    ll.append(5)
    ll.append("def")
    print(ll.head,ll.tail)
    
    for x in ll.iternodes(True):
        print(x)
        
    print("=======================")
    
    ll.remove(6)
    ll.remove(5)
    ll.remove(0)
    ll.remove(1)
    
    for x in ll.iternodes():
        print(x)
        
    print("``````````````````````````````````````")
    
    ll.insert(3,5)
    ll.insert(20,"def")
    ll.insert(1,2)
    ll.insert(0,"abc")
    for x in ll.iternodes():
        print(x)
        
    len(ll)

    进阶题

    实现类property装饰器,类名称为Property。

    基本结构如下,是一个数据描述器

    class Property:#数据描述器
        def __init__(self):
            pass
        def __get__(self,instance,owner):
            pass
        def __set__(self,instance,value):
            pass
        
    class A:
        def __init__(self,data):
            sef._data = data
            
        @Property
        def data(self):
            return self._data
        
        @data.setter
        def data(self,value):
            self._data = value
    class Property:#数据描述器
        def __init__(self,fget,fset = None):
            self.fget = fget
            self.fset = fset
        def __get__(self,instance,owner):
            if instance is not None:
                return self.fget(instance)
            return self
        
        def __set__(self,instance,value):
            if callable(self.fset):
                self.fset(instance,value)
            else:
                raise AttributeError()
            
        def setter(self,fn):
            self.fset = fn
            return self
        
        
    class A:
        def __init__(self,data):
            self._data = data
            
        @Property#data = Property(data)=》data = obj
        def data(self):
            return self._data
        
        @data.setter#data = data.setter(data) ==> data = obj
        def data(self,value):
            self._data = value
            
    a = A(100)
    print(a.data)
    a.data = 200
    print(a.data)
  • 相关阅读:
    【Linux】ZeroMQ 在 centos下的安装
    ZeroMQ下载、编译和使用
    在Linux系统上安装Git
    Linux下python2.7安装pip
    [Tyvj1474]打鼹鼠
    [BZOJ2908]又是nand
    [SPOJ375]Qtree
    浅谈算法——树链剖分
    [BZOJ5368/Pkusc2018]真实排名
    [FJOI2007]轮状病毒
  • 原文地址:https://www.cnblogs.com/xpc51/p/11798435.html
Copyright © 2011-2022 走看看