描述器 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)

 

posted on 2019-11-21 23:35  xpc199151  阅读(253)  评论(0编辑  收藏  举报

导航