Pthon魔术方法(Magic Methods)-描述器

      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  
描述器版本参考案例

 

posted @ 2019-08-22 00:34  尹正杰  阅读(371)  评论(0编辑  收藏  举报