什么是描述器呢?近来在复习python 面向对象的笔记,正好温故而知新。
描述器
描述器就是 可以描述一个属性操作的对象。
举例说明
比如有一个类, 类属性 age , 可以在类里面进行修改, 也可以在类外面用 类名.age = 新值进行想修改。
但是这样是不安全的, 但我们也没法对其进行拦截,我们需要对这种数据进行 校验 , 不符合业务逻辑的,就不允许修改。
引出描述器
此时我们要想完成这种对数据进行拦截, 经过校验后才运行赋值的操作,要怎么实现呢?这个时候, 就可以借助描述器了。
描述器的方法
描述器里有三个方法, __set__ , __get__ 和 __delete__
有了描述器之后, 所有的在类的外部对类属性进行修改,删除,获取的操作都会被解析器自动转换成描述器的操作,
这样就可以对数据进行过滤, 保护等
描述器的定义方式(一)
class People:
def __init__(self):
self.__age = 0
def get_age(self):
return self.__age
def set_age(self, value):
if int(value) > 0:
self.__age = value
def del_age(self):
del self.__age
age = property(get_age, set_age, del_age)
p = People()
p.age = 10
print(p.age)
del p.age
# AttributeError: 'People' object has no attribute '_People__age'
# 已经删除了 age属性了,再去获取,就会报错
# print(p.age)
通过 help 来看到类的说明文档
class People:
def __init__(self):
self.__age = 0
def get_age(self):
return self.__age
def set_age(self, value):
if int(value) > 0:
self.__age = value
def del_age(self):
del self.__age
age = property(get_age, set_age, del_age)
p = People()
p.age = 10
# print(p.age)
help(People)
运行效果
class People(builtins.object)
| Methods defined here:
|
| __init__(self)
| Initialize self. See help(type(self)) for accurate signature.
|
| del_age(self)
|
| get_age(self)
|
| set_age(self, value)
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| age
进程已结束,退出代码 0
可以看到age 是属于数据描述器
使用 @property 装饰器来实现描述器
class People:
def __init__(self):
self.__age = 0
@property
def age(self):
return self.__age
# 方法名必须一致, 通过装饰器区分是设置还是删除
@age.setter
def age(self, value):
if int(value) > 0:
self.__age = value
@age.deleter
def age(self):
del self.__age
p = People()
p.age = 12
print(p.age)
这个 实现方法有个弊端, 对一个属性的操作需要用到三个方法, 如果对三个属性进行操作, 就要用到九个方法, 这样是很不方便的
描述器的定义方式(二)把对一个属性的操作都封装到一个类里
class Age:
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age=28
print(people.age)
del people.age
运行效果
set
get
None
delete
从这里可以看出, 只是方法调用了, 没有存储宿主类的数据。 后面会讲怎么做的,这里先不管。
注意事项:一般是 通过宿主类的实例对象进行调用
class Age:
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
print("look + " + str(People.age))
# 不会起作用的
del People.age
运行效果
get
look + None
由于描述器是宿主类的类属性,是可以通过类名进行访问, 但是由于参数是self, 所以也就只有获取可以调用,而且这个获取是只针对类的属性进行获取,设置和删除的 方法是调用不了的。
因此描述器一般都是通过宿主类的实例化对象进行使用的。
实例属性的访问顺序和描述器的拦截
一个实例属性的正常访问顺序
从对象自身的__dict__ 开始找 >> 对应类的__dict__开始找>> 父类的__dict__ 开始找 >> 如果没找到,又定义了 __getattr__方法就会调用这个方法
如何实现将描述器的get 方法给嵌入到查询机制呢?
是通过 __getattribute__ 方法实现的
内部就是先找有没有描述器方法__get__ , 如果有就调用,没有就按照上面的机制去查找(也就是描述器的检测优先)
拦截
那么既然知道了实现的原理是通过 __getattribute__ , 我们就可以通过在宿主类中手动设置 __getattribute__ 进行拦截,会把描述器的__get__方法给拦截了
拦截代码示范
class Age:
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
def __getattribute__(self, item):
print("get方法被我拦截了")
def __setattr__(self, key, value):
print("set 方法也拦截了")
def __delattr__(self, item):
print("del 方法也拦截了")
people = People()
people.age = 85
print(people.age)
del people.age
运行效果
set 方法也拦截了
get方法被我拦截了
None
del 方法也拦截了
当然既然描述器的get 可以拦截, 那么set 和 del 也是可以拦截的
描述器的操作方法是优先于python的对象属性查询机制的
从上面的案例就可以得出这个结论,描述器的操作方法是优先于python的对象属性查询机制的
描述器的优先级问题
描述器分为两种, 资料描述器和非资料描述器
什么是资料描述器, 就是实现有 get 和 set 方法的 就是资料描述器;
而非资料描述器是仅仅实现了 get 方法的.
优先级
资料描述器 >> 实例属性 >> 非资料描述器
资料描述器 >> 实例属性
class Age:
def __get__(self, instance, owner):
print("get")
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.__dict__)
运行效果
set
{}
结论
print(people.__dict__) 显示的为空,说明 age 就是取的资料描述器
实例属性 >> 非资料描述器
class Age:
def __get__(self, instance, owner):
print("get")
# 非资料描述器是没有set 和 delete 方法的
# def __set__(self, instance, value):
# print("set")
#
# def __delete__(self, instance):
# print("delete")
class People:
age = Age()
people = People()
# 这里反而成了给实例对象加了实例属性
people.age = 85
print(people.__dict__)
运行效果
{'age': 85}
结论
从运行结果可以看出, 这里是实例属性的优先级更高
描述器的存储问题:描述器的数据存储都是存在宿主类的实例对象里
class Age:
def __get__(self, instance, owner):
print("get")
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
运行效果
set
get
None
当前只是实现了描述器的使用, 但是并没有真正 的将数据存储 到 内存中
class Age:
def __init__(self):
self.__age = 0
def __get__(self, instance, owner):
print("get")
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
运行效果
self 是 <__main__.Age object at 0x0000020BB95C5AF0> , instance 是 <__main__.People object at 0x0000020BB9609280> , value 是 85
set
get
None
这样看出, self 是 Age的实例化对象 age, instance是People实例对象 people, value是要修改的数值.
实例化一个P2 , 对描述器进行赋值 的 时候 ,也是走这个方法.
class Age:
def __init__(self):
self.__age = 0
def __get__(self, instance, owner):
print("get")
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
print("set")
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
p2 = People()
p2.age = 100
print(p2.age)
运行效果
self 是 <__main__.Age object at 0x00000169A9185AF0> , instance 是 <__main__.People object at 0x00000169A91C9280> , value 是 85
set
get
None
self 是 <__main__.Age object at 0x00000169A9185AF0> , instance 是 <__main__.People object at 0x00000169A91C9850> , value 是 100
set
get
None
可以看到age 对象都是同一个内存地址, 但是 People实例对象的地址不一样,
因此当前 age 是作为People的类属性 的, 所有的People实例对象都共同拥有 age 属性
如果在在描述器中, 把要修改的值都绑定到self 身上(self 就是Age的实例对象本身), 会怎样?
class Age:
def __init__(self):
self.__age = 0
def __get__(self, instance, owner):
print("get")
# 不返回, 宿主类的实例对象拿不到值
return self.v
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
print("set")
self.v = value
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
p2 = People()
# p2.age = 100
# 此时p2.age 会是 85
print(p2.age)
这样就会导致所有的实例对象共享age对象的属性,这样如果有一个实例对象修改了, 另一个实例对象就会受到波及,数据不安全.
绑到 instance 身上(宿主类的实例对象)
class Age:
def __init__(self):
self.__age = 0
def __get__(self, instance, owner):
print("get")
# 不返回, 宿主类的实例对象拿不到值
return instance.v
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
print("set")
instance.v = value
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
p2 = People()
# AttributeError: 'People' object has no attribute 'v'
# p2.age = 100
# 如果注释了 p2.age = 100,就会报错。 描述器在未赋值之前, 直接使用 描述器的get方法就会报错没有这个属性
# print(p2.age)
为了运行验证,可以执行如下代码
class Age:
def __init__(self):
self.__age = 0
def __get__(self, instance, owner):
print("get")
# 不返回, 宿主类的实例对象拿不到值
return instance.v
# 非资料描述器是没有set 和 delete 方法的
def __set__(self, instance, value):
print("self 是 %s , instance 是 % s , value 是 %s" %(self, instance, value))
print("set")
instance.v = value
def __delete__(self, instance):
print("delete")
class People:
age = Age()
people = People()
people.age = 85
print(people.age)
p2 = People()
# AttributeError: 'People' object has no attribute 'v'
p2.age = 100
# 如果注释了 p2.age = 100,就会报错。描述器在未赋值之前, 直接使用 描述器的get方法就会报错没有这个属性
print(p2.age)
运行效果
self 是 <__main__.Age object at 0x000001ED77F15AF0> , instance 是 <__main__.People object at 0x000001ED77F594C0> , value 是 85
set
get
85
self 是 <__main__.Age object at 0x000001ED77F15AF0> , instance 是 <__main__.People object at 0x000001ED77F59850> , value 是 100
set
get
100
进程已结束,退出代码 0