描述器由一个类对象定义,实现了__get__
方法,__set__
, __delete__
方法的类对象叫作描述器类对象,咱们指的描述器是指这个类的实例对象。python
描述器对象可以实现了两个类的交互做用,将其中的一个类操做本身属性的行为转而映射到另外一个类的一个方法上,实现更多灵活的操做。ide
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 属性的操做,不然将再次出发描述器,进而产生递归。 在描述器想实现对实例属性的增长或者访问,应该操做该实例属性字典来避免递归现象code
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
上面的程序虽然调用了描述器,可是描述器中的操做和普通赋值取值操做一致,在外部使用时感受不到描述器的存在。这样咱们就能够这些属性进行赋值时对参数进行一些限制了。例如实现一个参数的类型检测功能。orm
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