• 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  
    描述器版本参考案例
  • 相关阅读:
    Codeforces Round #424 (Div. 2, rated, based on VK Cup Finals) Problem A
    Codeforces Round #423 (Div. 2, rated, based on VK Cup Finals) Problem E (Codeforces 828E)
    Codeforces 828D High Load
    Codeforces Round #423 (Div. 2, rated, based on VK Cup Finals) Problem C (Codeforces 828C)
    Codeforces Round #423 (Div. 2, rated, based on VK Cup Finals) Problem A
    2017.7.11 图论测试
    poj 3683 Priest John's Busiest Day
    poj 3207 Ikki's Story IV
    hdu 1811 Rank of Tetris
    hdu 2647 Reward
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/11392101.html
Copyright © 2020-2023  润新知