描述器 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进行数据校验。
思路
- 写函数,在__init__中先检查,如果不合格,直接抛异常。
- 装饰器,使用inspect模块完成
- 描述器
#写函数检查 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
作业
将前面的链表,封装成容器
要求
- 提供__getitem__,__iter__方法,__setitem__方法
- 使用一个列表,辅助完成上面的方法
- 进阶:不使用列表,完成上面的方法
进阶题
实现类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的主要内容,如果未能解决你的问题,请参考以下文章
解决报错:在Python中使用property装饰器时,出现错误:TypeError: descriptor ‘setter‘ requires a ‘property‘ object but(代码片