描述器 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进行数据校验。
思路
- 写函数,在__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): 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
作业
将前面的链表,封装成容器
要求
- 提供__getitem__,__iter__方法,__setitem__方法
- 使用一个列表,辅助完成上面的方法
- 进阶:不使用列表,完成上面的方法
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)