Pthon魔术方法(Magic Methods)-描述器
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.描述器概述
1>.描述器定义
Python中,一个类实现了"__get__","__set__","__delete__"三个方法中的任何一个方法,就是描述器。 实现着三个中的某些方法,就支持了描述器协议:
仅实现了"__get__",就是非数据描述器,即non-data descriptor
实现了"__get__","__set__"就是数据描述器,即data descriptor
"__delete__"方法有同样的效果,有了这个方法,也是数据描述器。
如果一个类属性设置为描述器实例,那么它被称为owner属主。
当该类的该类属性被查找,设置,删除时,就会调用描述器相应的方法。
2>.非数据描述器案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class A: 7 def __init__(self): 8 self.a1 = "a1" 9 print("A.init") 10 11 def __get__(self, instance, owner): 12 """ 13 因为定义了"__get__"方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用"__get__"方法 14 参数说明如下: 15 self指代当前实例对象,即调用者,在本例中它对应的是A的实例 16 instance是owner的实例,在本例中它的值可能有两种: 17 None表示不是B类的实例,对应调用B.x 18 <__main__.B object at 0x00000284CBF675C8>表示是B的实例,对应调用B().x 19 owner是属性的所属的类 20 """ 21 print("A.__get__ {} {} {}".format(self,instance,owner)) 22 return self 23 24 class B: 25 x = A() #此时我们可以说x是非数据描述器,因为A()类中仅实现了"__get__"方法 26 def __init__(self): 27 print("B.init") 28 self.x = "b.x" #增加实例属性"x",由于这里的实例属性名称和上面的非数据描述器名称一致,此时赋值即定义,实例x变量会将类中的描述器标识符覆盖(因为A类中没有"__set__"方法可调用)。 29 30 31 32 print("-" * 20) 33 print(B.x) 34 print(B.x.a1) 35 36 print("=" * 20) 37 b = B() 38 print(b.x) 39 # print(b.x.a1) #由于此时"b.x"访问到的是实例的属性,而不是非数据描述器,因此报错"AttributeError: 'str' object has no attribute 'a1'"
A.init -------------------- A.__get__ <__main__.A object at 0x000001536AD65588> None <class '__main__.B'> <__main__.A object at 0x000001536AD65588> A.__get__ <__main__.A object at 0x000001536AD65588> None <class '__main__.B'> a1 ==================== B.init b.x
3>.数据描述器案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class A: 7 def __init__(self): 8 self.a1 = "a1" 9 print("A.init") 10 11 def __get__(self, instance, owner): 12 """ 13 因为定义了"__get__"方法,类A就是一个描述器,使用类B或者类B的实例来对x属性读取,就是对类A的实例的访问,就会调用"__get__"方法 14 """ 15 print("A.__get__ {} {} {}".format(self,instance,owner)) 16 return self 17 18 def __set__(self, instance, value): 19 """ 20 因为同时定义了"__get__"和"__set__"方法,类A就是一个数据描述器, 21 """ 22 print("A.__set__ {} {} {}".format(self,instance,value)) 23 self.data = value 24 25 class B: 26 x = A() #这里就是一个数据描述器 27 def __init__(self): 28 print("B.init") 29 self.x = "b.x" #增加实例属性"x",由于这里的实例属性名称和上面的数据描述器名称一致,因此会调用上面的"__set__"方法 30 31 32 33 print("-" * 20) 34 print(B.x) 35 print(B.x.a1) 36 37 print("=" * 20) 38 b = B() 39 print(b.x) 40 print(b.x.a1) 41 print(b.x.data) 42 b.x = 100 #这是调用数据描述器的"__set__"方法,或调用非数据描述器的实例覆盖 43 print(b.x) 44 B.x = 600 #赋值即定义,这是覆盖类属性,把描述器给替换了 45 print(B.x) 46 print(b.__dict__) 47 print(B.__dict__)
A.init -------------------- A.__get__ <__main__.A object at 0x000001D054546708> None <class '__main__.B'> <__main__.A object at 0x000001D054546708> A.__get__ <__main__.A object at 0x000001D054546708> None <class '__main__.B'> a1 ==================== B.init A.__set__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> b.x A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'> <__main__.A object at 0x000001D054546708> A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'> a1 A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'> b.x A.__set__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> 100 A.__get__ <__main__.A object at 0x000001D054546708> <__main__.B object at 0x000001D054546808> <class '__main__.B'> <__main__.A object at 0x000001D054546708> 600 {} {'__module__': '__main__', 'x': 600, '__init__': <function B.__init__ at 0x000001D054535438>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
4>.属性查找顺序
实例的"__dict__"优先于非数据描述器
数据描述器优先于实例的"__dict__"
5>.新增方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class A: 7 def __init__(self): 8 print("A.init") 9 10 def __get__(self, instance, owner): 11 print(1,self,instance,owner) 12 return self 13 14 def __set_name__(self, owner, name): 15 """ 16 提供在这个方法,就是可以知道主类和属主类的类属性名。 17 """ 18 print(2,self,owner,name) 19 self.name = name 20 21 22 class B: 23 test_name = A() #类属性创建时调用描述器的"__set_name__"方法 24 25 26 print("=" * 30) 27 print(B().test_name.name)
A.init 2 <__main__.A object at 0x000002082F5F5488> <class '__main__.B'> test_name ============================== 1 <__main__.A object at 0x000002082F5F5488> <__main__.B object at 0x000002082F5F5588> <class '__main__.B'> test_name
二.Python中的描述器
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class A: 7 8 def __init__(self): #非数据描述器 9 self.foo = 100 10 self.bar = 200 11 """ 12 由于foo和bar标识符调用的类装饰器非数据描述器,因此可以自行修改,而test标识符是数据描述器,此处修改会调用 13 "property"类的"__set__"方法,不允许修改test标识符,因此会报错:"AttributeError: can't set attribute"。 14 """ 15 # self.test = 300 16 17 def getfoo(self): #非数据描述器 18 return self.foo 19 20 21 @classmethod #非数据描述器 22 def foo(cls): 23 pass 24 25 @staticmethod #非数据描述器 26 def bar(): 27 pass 28 29 @property #数据描述器 30 def test(self): 31 return 100 32 33 34 a = A() 35 print(a.__dict__) 36 print(A.__dict__)
{'foo': 100, 'bar': 200} {'__module__': '__main__', '__init__': <function A.__init__ at 0x000002ABBE125678>, 'getfoo': <function A.getfoo at 0x000002ABBE1251F8>, 'foo': <classmethod object at 0x000002ABBE136788>, 'bar': <staticmethod object at 0x000002ABBE1367C8>, 'test': <property object at 0x000002ABBE072228>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
三.小试牛刀
1>.实现StaticMethod装饰器,完成staticmethod装饰器的功能
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class StaticMethod: 7 def __init__(self,fn): 8 self.fn = fn 9 10 def __get__(self, instance, owner): 11 return self.fn 12 13 class A: 14 @StaticMethod 15 def show(): 16 print("A.show static method") 17 18 A.show() 19 A().show() 20 21 22 23 #以上代码执行结果如下: 24 A.show static method 25 A.show static method
2>.实现ClassMethod装饰器,完成classmethod装饰器的功能
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 from functools import partial 7 8 class ClassMethod: 9 def __init__(self,fn): 10 self._fn = fn 11 12 def __get__(self, instance, cls): 13 res = partial(self._fn,cls) 14 return res 15 16 class A: 17 @ClassMethod 18 def show(cls): 19 print(cls.__name__) 20 21 print(A.__dict__) 22 A.show 23 A.show() 24 25 26 27 #以上代码执行结果如下: 28 {'__module__': '__main__', 'show': <__main__.ClassMethod object at 0x000001BB1C666548>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None} 29 A
3>.对实例的数据进行校验
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 class Person: 7 def __init__(self,name:str,age:int): 8 self.name = name 9 self.age = age 10 """ 11 对上面的类的实例属性name,age进行数据校验 12 13 思路: 14 1>.写函数,在__init__中先检查,如果不合格,直接抛异常 15 2>.装饰器,使用inspect模块完成 16 3>.描述器 17 """
#!/usr/bin/env python #_*_conding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie class Person: def __init__(self,name:str,age:int): params = ((name,str),(age,int)) if not self.checkdata(params): raise TypeError("传入参数树类型错误,请检查数据类型,要求传入参数为--->name:str,age:int") self.name = name self.age = age def checkdata(self,params): for param,typ in params: if not isinstance(param,typ): return False return True p1 = Person("Jason Yin","18")
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import inspect 7 8 class TypeCheck: 9 def __init__(self,name,typ): 10 self.name = name 11 self.type = typ 12 13 def __get__(self, instance, owner): 14 print("TypeCheck.get") 15 if instance: 16 return instance.__dict__[self.name] 17 return self 18 19 def __set__(self, instance, value): 20 print("TypeCheck.set") 21 if not isinstance(value,self.type): 22 raise TypeError(value) 23 instance.__dict__[self.name] = value 24 25 class PropsInject: 26 def __init__(self,cls): 27 self.cls = cls 28 sig = inspect.signature(cls) 29 params = sig.parameters 30 for name,parm in params.items(): 31 print(name,parm) 32 if parm.annotation != parm.empty: #注入类属性 33 setattr(cls,name,TypeCheck(name,parm.annotation)) 34 35 def __call__(self, *args, **kwargs): 36 return self.cls(*args,**kwargs) #新构建一个新的Person对象 37 38 39 @PropsInject 40 class Person: 41 def __init__(self, name: str, age: int): 42 self.name = name 43 self.age = age 44 45 46 print(Person.__dict__) 47 p1 = Person("Jason Yin",18) 48 p2 = Person("Jason Yin","26") 49 50