Python描述器
2019-10-26 11:21
1696 查看
描述器由一个类对象定义,实现了
__get__方法,
__set__,
__delete__方法的类对象叫做描述器类对象,我们指的描述器是指这个类的实例对象。
描述器对象能够实现了两个类的交互作用,将其中的一个类操作自己属性的行为转而映射到另一个类的一个方法上,实现更多灵活的操作。
class A: # 这是一个描述器类 def __get__(self, instance, owner): pass def __set__(self, instance, value): pass def __delete__(self, instance): pass class B: x = A() def __init__(self): self.x = 123 # 使用B类直接调用x 方法 B.x # 本应该返回A(),但是由于A()是一个描述器对象,将会自动调用A()的__get__方法获取返回值 # 使用B类实例调用 b = B() b.x # 由于A是数据描述器,实例化中的x 和 类中的 x 属性同名,self.x = 123, 将会触发转而调用A类中的`__set__`方法,instance为B类的self实例,value为值123
A 类是一个描述器类,当A的实例作为其他对象的的一个属性时,其他对象对该属性进行操作时,将会调用这个描述类中对应操作的
__get__,
__set__,
__delete__方法。
非数据描述器和数据描述器
非数据描述器是指只实现了
__get__方法的描述器,类属性名将不会影响实例属性的赋值操作,当实例属性和类属性同名时候,实例依然优先访问自己的属性值.
class A: # 这是一个描述器类 def __get__(self, instance, owner): pass class B: x = A() # x 和 y 是两个不同的实例描述器实例对象 y = A() def __init__(self): self.x = 123 # A是数据描述器,该语句正常执行 b = B() b.x # 123优先访问自己的x属性, 返回值 123 b.y # 由于b没有y属性,调用B的类属性y,便调用了A类中__get__函数获取返回值 # 同时参数instance为b,owner为b的类,即B B.x # 调用A中的__get__,由于是B类调用x,instance参数为None,owner为B
数据描述器在实现了
__set__方法的基础上,还实现了
__set__或
__delete__方法其中的至少一个。当类属性关联一个属性描述器时,通过实例访问与描述器同名的属性时候,仍然会触发数据描述器,转而调用描述器中的
__get__,
__set__,
__delete__方法,实例对象自己的属性将不能直接访问。
class A: # 数据描述器类 def __get__(self, instance, owner): print("get") def __set__(self, instance, value): print("set") class B: x = A() def __init__(self): self.x = 123 b = B() print(b.x) ----- 执行结果分析 ------- b = B() 初始化一个B实例,调用__init__初始化方法,执行self.x = 123, 由于B类的 x属性是一个数据描述器,实例对 x 属性的访问仍然会被描述器拦截,self.x = 123 将会转而调用A类中的__set__方法(因为这是赋值操作调用__set__,访问操作调用__get__),将会打印__set__方法中的"set" print(b.x) 通过b.x 访问x属性时同样被描述器拦截,对应调用描述器__get__方法,打印__get__中的"get",并返回None值,故print(b.x)打印None
Note:在使用反射函数
setattr(b, "x", 123)时,效果如同
b.x = 123,将会调用描述器,所以在描述器中不要出现
instance.x = 123类似的使用实例访问 x 属性的操作,否则将再次出发描述器,进而产生递归。 在描述器想实现对实例属性的增加或者访问,应该操作该实例属性字典来避免递归现象
class A: # 数据描述器类 def __init__(self, args): self.args = args def __get__(self, instance, owner): return instance.__dict__(self.args) def __set__(self, instance, value): instance.__dict__(self.args) = value class B: x = A("x") def __init__(self): self.x = 123
上面的程序虽然调用了描述器,但是描述器中的操作和普通赋值取值操作一致,在外部使用时感觉不到描述器的存在。这样我们就可以这些属性进行赋值时对参数进行一些限制了。例如实现一个参数的类型检测功能。
import inspect class A: # 数据描述器类 def __init__(self, args, typ): self.args = args self.typ = typ def __get__(self, instance, owner): return instance.__dict__(self.args) def __set__(self, instance, value): # 在赋值前对参数进行检测,满足条件才添加到字典中 if isinstance(value, self.typ): instance.__dict__(self.args) = value else: raise TypeError("'{}' need the type of {}".format(self.args, self.typ)) def get_type(cls): sig = inspect.signature(cls) for name, parmas_obj in sig.parameters.items(): if params.annotation is not sig.empty: # 定义了参数注解才会进行检测 class B: x = A("x", int) def __init__(self, x:int): self.x = x # 赋值调用描述器
上面编码是实现了对
x参数的类型检查,在描述器中
__set__方法中实现了参数的类型检查,这样即使在以后对实例的
x属性进行重新赋值,仍然会再次检查新赋值的类型。
上面代码采用硬编码对x属性进行检查,可以使用一个装饰器对B类动态添加需要检测的属性。
class TypCheck: def __init__(self, name, typ): self.name = name self.typ = typ def __get__(self, instance, owner): if instance is None: # 通过类名调用描述器属性时 instance为None值 raise TypeError("类名无法调用该方法,只支持实例调用") return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, self.typ): raise TypeError("{} {}".format(self.name, self.typ)) instance.__dict__[self.name] = value def inject(cls): # 装饰器,为A类动态注入类似于上例中x = A(x, int)类型检查的描述器 sig = inspect.signature(cls) for name, typ in sig.parameters.items(): if typ.annotation is not sig.empty: # cls.__dict__[name] = Typ_check(name, typ.annotation) setattr(cls, name, TypCheck(name, typ.annotation)) print(cls.__dict__) return cls @Inject class A: def __init__(self, name: str, age: int): self.name = name self.age = age # 简单测试 a = A("name", 10) print(a.name) print(a.age) # 当参数类型不匹配时 a.name = 12 # TypeError a.age = "12" # TypeError
相关文章推荐
- Python语言描述随机梯度下降法
- Python描述符:property()函数的小秘密
- 一个无聊男人的疯狂《数据结构与算法分析-C++描述》学习笔记 用C++/lua/python/bash的四重实现(7)习题2.8 随机数组的三种生成算法
- 数据结构与算法+Python语言描述pdf
- 解密Python中的描述符(descriptor)
- Python:画图(坐标描述,title,图大小,曲线标号)
- Python对list的操作描述
- Python语言描述KNN算法与Kd树
- 计算机视觉——python3的SIFT与harris特征匹配及sift原理描述
- SICP Python 描述 3.4 异常
- [Python嗯~机器学习]---用python3来描述sklearn的基本使用
- Python之美[从菜鸟到高手]--玩转描述符和属性
- Python入门笔记—第十一章【面向对象之OOP(第五部分,类相关函数&成员描述符&成员属性)】
- Python之美[从菜鸟到高手]--玩转描述符和属性
- python爬虫——关于ajax加载之爬取2019年知乎问题和描述
- 小甲鱼python046魔法方法:描述符(property 的原理)
- 实战丨Python黑魔法之描述符
- Python语言描述机器学习之Logistic回归算法
- Python对list的操作描述
- 用Python进行常见的描述统计