python 描述器
2018-02-02 本文已影响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、这是一种猴子补丁技术