描述器 descriptors

Posted xpc51

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了描述器 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装饰器的功能

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

#类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,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. 进阶:不使用列表,完成上面的方法

 进阶题

实现类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

 

 



 

 
 

以上是关于描述器 descriptors的主要内容,如果未能解决你的问题,请参考以下文章

描述器 descriptors

解决报错:在Python中使用property装饰器时,出现错误:TypeError: descriptor ‘setter‘ requires a ‘property‘ object but(代码片

python学习笔记-类的descriptor

python笔记63 - __get__ 描述器(descriptor)学习

Python描述符(descriptor)解密

Python描述符(descriptor)解密(转)