python 描述器

作者: 宝宝家的隔壁老王 | 来源:发表于2018-02-02 00:55 被阅读12次

    描述器定义

    ① 实现描述符协议
    • 实现 __get__(), __set__(), __delete__() 方法
    ② 实例&类属性、方法的访问
    • 1、类和实例的访问
      # 标准的描述符定义
      class Quanty:
          def __init__(self, weight):
              self.weight = weight
          
          def __get__(self, instance, cls):
              return self.weight
              
          def __set__(self, instance, value):
              self.weight = value
      
      class Person:
          name = 'PY_ONE'
          weight = Quanty(90)  # 描述器实例
          
          def __init__(self, age):
              self.age = age
          
          @classmethod
          def get_verson(cls):
              return 1.0
          
          @staticmethod
          def find_version():
              return 1.0
      
      • 使用 vars()__dict__查看对象的属性
      # Person 类对象
      >>> vars(Person)
      >>> mappingproxy({
          '__dict__': <attribute '__dict__' of 'Person' objects>,
          '__doc__': None,
          '__init__': <function __main__.Person.__init__>,
          '__module__': '__main__',
          '__weakref__': <attribute '__weakref__' of 'Person' objects>,
          'find_version': <staticmethod at 0x10544ed30>,
          'get_verson': <classmethod at 0x10544e240>,
          'name': 'PY_ONE',
          'weight': <__main__.Quanty at 0x105472cc0>
      })
      
      # Person 实例对象
      >>> per = Person(18)
      >>> vars(per)
      >>> {'age': 18}
      
      # 可以看到,实例对象 per 只有 age 一个属性,类 Person 有 name, get_version, find_version, weight 属性
      
      • 类属性可以使用实例或类对象访问,只有类才能修改
      >>> p1, p2 = Person(16), Person(17)
      >>> p1.name  # PY_ONE
      >>> p2.name  # PY_ONE
      >>> Person.name  # PY_ONE
      
      >>> p1.name = 'PY_WJ'
      >>> Person.name  # PY_ONE
      >>> p2.name  # PY_ONE
      >>> p1.name  # PY_ONE
      
      >>> Person.name = '_PY'
      >>> p2.name  # _PY
      >>> p1.name  # PY_WJ
      
      • 属性访问的原理与描述器
      # 属性访问就是基于 Python 的描述器实现。类或者实例通过 . 操作符访问属性,会先访问对象的 __dict__,如果没有再访问类(或父类,元类除外)的 __dict__。如果最后这个 __dict__ 的对象是一个描述器,则会调用描述器的 __get__ 方法
      
      >>> p2.name  # 等同于调用 p2.__dict__,发现 name 不在此属性字典中,则尝试调用 type(p2).__dict__,发现此属性存在,且此属性的对象是字符串,故为 type(p2).__dict__['name']
      
      >>> p2.weight  # 和上述调用一致,不过在 type(p2).__dict__['weight']的对象是一个描述器,故最终调用如下 type(p2).__dict__['weight'].__get__(p2, type(p2))
      
      • 类方法的调用
      >>> p1.get_version  # 调用同上,发现 p1 实例中没有 get_version 属性,且 type(p1).__dict__['get_version'] 的对象是 classmethod 实例,故最终调用如下 type(p1).__dict__['get_version'].__get__(Person)
      
      • 静态方法同类方法
      # 同类方法的调用
      
    • 2、使用描述符对类属性做验证
      • 示例如下
      # 验证正数的描述器
      class PositiveNum:
          
          def __init__(self, key_col):
              self._key_col = key_col
          
          def __get__(self, instance, cls):
              return instance.__dict__[self._key_col]
              
          def __set__(self, instance, value):
              if value < 0:
                  raise ValueError('value must be > 0')
              instance.__dict__[self._key_col] = value
      
      # 商品类
      class Good:
          price = PositiveNum('price')
          quantity = PositiveNum('quantity')
          
          def __init__(self, price, quantity):
              self.price = price
              self.quantity = quantity
      
      >>> g = Good(12, -1)  # ValueError
      >>> g = Good(12, 8)  # {'price': 12, 'quantity': 8}
      
      # 疑问点 a
      >>> g.price = -1  # ValueError:  会执行type(g).__dict__['price'].__set__(g, -1)
      
      # 疑问点 b
      >>> Good.price = -1  # TODO 
      
    • 3、疑问点解释
      • 疑问点 a
      # 只是对类属性声明为描述符对象,为何对实例属性进行赋值操作的时候,也会被描述符覆盖?
      
      答: 
      1、描述符分为覆盖型描述符和非覆盖型描述符,而判断是否为覆盖型描述符的条件就是该描述符是否实现了 __set__(self, obj, value) 方法。
      2、对于同名实例属性,如上文的 price 和 quantity 属性,如果描述符为覆盖型描述符,则描述符同样会覆盖对实例属性的复制操作。
      3、当描述符和实例字典中的某个属性重名,按访问优先级为 覆盖型描述符 > 同名实例字典中属性 > 非覆盖型描述符
      
      • 疑问点 b
      # 类属性已声明为描述符对象,为何对类属性赋值会覆盖描述符?
      
      答:
      1、不管描述符是不是覆盖型描述符,对类属性赋值都能覆盖描述符
      2、这是一种猴子补丁技术
      

    相关文章

      网友评论

        本文标题:python 描述器

        本文链接:https://www.haomeiwen.com/subject/rbfszxtx.html