描述器 Descriptors
定义
- 在Python中,类中实现了魔术方法
__get__ __set__ __delete__
三者中其一则称其为描述器。 - 当一个类的属性为描述器时,访问这些属性会受到以上三者的控制。
魔术方法
只有当实现的类作为一个类的属性时,以下方法才起作用
实现__get__
方法则称为非数据描述器
实现__get__
和其他二者其一称为数据描述器
方法 | 参数 | 作用 |
---|---|---|
__get__ | instance:属主实例 owner:属主类型 | 当实现类作为类属性时,通过类或者实例访问该属性时会调用该方法。访问到的值为该方法的返回值。 |
__set__ | instance value:要修改的值,即等号右侧的值 | 当实现类作为类属性时,通过实例修改该属性时会调用该方法。实现该方法后,实例访问描述器属性时会优先访问类的。 |
__delete__ | instance | 当实现类作为类属性时,通过实例删除该属性时会调用该方法。实现该方法后,实例访问描述器属性时会优先访问类的。 |
class A:
def __get__(self, instance, owner):
print('get')
def __set__(self, instance, value):
print('set')
def __delete__(self, instance):
print('delelte')
class B:
a = A()
b = B()
B.a
b.a
b.a = 1
del b.a
>>>get
>>>get
>>>set
>>>delelte
通过B的类或实例在访问类的属性a时,由于a的类定义了__get__
方法,会对其调用。
由于A定义了__set__
方法,B的实例访问a属性时会优先访问类的属性,此时会调用__set__
方法,而不会给实例新增属性。
作用
装饰器property
通过使用装饰器property,可以像访问类属性的方式来调用get、set、del方法,对属性访问加以控制。
class Property:
def __init__(self, getter, setter=None, deleter=None):
self.__get = getter
self.__set = setter
self.__del = deleter
def setter(self, set):
self.__set = set
return self
def deleter(self, dele):
self.__del = dele
return self
def __get__(self, instance, owner):
return self.__get(instance)
def __set__(self, instance, value):
self.__set(instance, value)
def __delete__(self, instance):
self.__del(instance)
class A():
def __init__(self):
self.__x = 1
@Property
def x(self):
return self.__x
@x.setter
def x(self, value):
self.__x = value
@x.deleter
def x(self):
del self.__x
a = A()
a.x
>>>1
-
在装饰get方法时,会调用Property类的初始化方法,将get方法加入到Property实例中,并返回Property实例对象。
-
通过返回的Property实例调用类方法setter和deleter来装饰set方法和del方法,返回值必须是该Property实例对象。
-
在A的属性中,x始终是一个Property对象。Property类是一个描述器,当使用实例来访问和修改属性时,就会调用Property的
__get__ __set__ __delete__
中的对应方法,在这些方法中会调用实例传进来的对应方法,从而达到像直接操作属性一般的效果。
类型检查
class Typed:
def __init__(self, name, type):
self.name = name
self.type = type
def __get__(self, instance, owner):
return instance.__dict__[self.name] # 只能通过字典来访问 其他方式都会触发__get__方法
def __set__(self, instance, value):
print(value,self.type,self.name)
if isinstance(value, self.type):
instance.__dict__[self.name] = value
else:
raise TypeError('{} must {} type'.format(self.name, self.type))
def __delete__(self, instance):
del instance.__dict__[self.name]
class Test():
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name, age):
self.name = name
self.age = age
利用描述器的特性,对类添加属性来控制实例的同名变量的修改。
直接在类中添加不够优雅
- 使用inspect模块中来查看参数注解,自动来获取参数名以及类型数据
- 使用装饰器来注入属性
import inspect
class Typed:
def __init__(self, name, type):
self.name = name
self.type = type
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
# print(value,self.type,self.name)
if isinstance(value, self.type):
instance.__dict__[self.name] = value
else:
raise TypeError('{} must {} type'.format(self.name, self.type))
def __delete__(self, instance):
del instance.__dict__[self.name]
# 使用函数装饰器,检查类初始化函数的参数名和类型注解来初始化Typed类动态写入到类中,并将其返回
def typeAssert(cls):
parameters = inspect.signature(cls).parameters
for name, par in parameters.items():
if par.annotation != par.empty:
setattr(cls, name, Typed(name, par.annotation))
return cls
# 使用类来做装饰器,将会返回一个对象。当把该实例当作类来实例化对象时,会在__call__中返回一个被装饰的类的实例
class TypeAssert:
def __init__(self, cls):
parameters = inspect.signature(cls).parameters
for name, par in parameters.items():
if par.annotation != par.empty:
setattr(cls, name, Typed(name, par.annotation))
self.__cls = cls
def __call__(self, *args, **kwargs):
return self.__cls(*args, **kwargs)
@TypeAssert
class Test():
# name = Typed('name', str)
# age = Typed('age', int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age