描述器由一个类对象定义,实现了__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
另外有需要云服务器可以了解下创新互联cdcxhl.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。