前言
以下是用我自己可以理解的方式去记录关于描述符的认识笔记。纯属整理各大佬们的文章和实践笔记记录。如有错误,还希望多多指教!
章节内容:
描述符的定义和作用 描述符属性的查询优先级问题
定义:
描述符它的本质是一个新式类,如果一个类里面如果包含实现了__ set__、__ get__、__ delete__之一或全部实现,那么可以说这个是描述符类。
作用:
所以我们的描述数其实主要作用可以看成是对类里面某个成员变量的进行精细化的管理操作。
绑定行为:所谓的绑定行为,是指在属性的访问、赋值、删除时还绑定发生了其他的事情,正如前面属性控制三剑客所完成的事情一样;
托管属性:python描述符是一种创建“托管属性”的方法,即通过描述符(类)去托管另一个类的相关属性,也可以说是类的属性的一个代理。为了方便的理解“托管属性”这个概念,将通过几个通俗的例子去说明。
---PS 上面关于绑定行为和托管属性的描述来自:
版权声明:CSDN博主「LoveMIss-Y」的原创文章 原文链接:https://blog.csdn.net/qq_27825451/article/details/84848341
分类:
数据描述符(完全实现):同时实现上面了__ set__、__ get__、__ delete__的类。 非数据描述符(不完全实现):没全部实现上面三个方法的类,仅实现__ set__方法
描述符的应用场景
描述符的本质对属性的个性定制与控制:所以通常相关的场景如:
(1)属性设置和获取时候检测,会修改。 (2)属性检测时候自定义异常描述 (3)标记相关属性为只读属性
示例说明:
1)参数异常示例
数据描述符的类:
# 描述的类
class Describe:
def __get__(self,):
pass
def __set__(self,):
pass
def __delete__(self):
pass
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
def say(self):
print("我开始说话")
p = People()
p.name = "A同学"
示例角色:
Describe:描述符类 People:被描述的类 People.name:被描述的类对象绑定的管理的变量
步骤分析:
首先 描述符类中的三个方法的作用是:
__ set__:调用属性时触发 __ get__:为属性赋值时触发 __ delete__:del删除属性时触发
上面当我们的执行:p.name = "A同学",此时会去调用Describe中的__set__(self,)方法:
但是我们的会提示参数异常:
Traceback (most recent call last):
File "D:/code/python/local_python/AntFlask/设计模式/描述符.py", line 29, in <module>
p.name = "A同学"
TypeError: __set__() takes 1 positional argument but 3 were given
异常说明:这个方法,需要三个参数,但是只给了1个参数,所以发生了,错误!!!
修改示例具体为我们需要关系的数据,打印具体的每个参数具体是什么:
# 描述的类
class Describe:
def __get__(self, instance, value):
print('gET第1个参数:', self)
print('gET第2个参数:', instance)
print('gET第3个参数:', value)
value = '我是要返回的值'
return value
# def __set__(self, instance, value):
def __set__(self, instance, value):
pass
print('SET第1个参数:',self)
print('SET第2个参数:', instance)
print('SET第3个参数:', value)
def __delete__(self,instance):
print("删除对象",instance)
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
def say(self):
print("我开始说话")
p = People()
print(p.name)
p.name = "A同学"
# 删除属性
del p.name
输出结果是:
gET第1个参数: <__main__.Describe object at 0x000001FD0725C940>
gET第2个参数: <__main__.People object at 0x000001FD0725C908>
gET第3个参数: <class '__main__.People'>
我是要返回的值
SET第1个参数: <__main__.Describe object at 0x000001FD0725C940>
SET第2个参数: <__main__.People object at 0x000001FD0725C908>
SET第3个参数:A同学
删除对象 <__main__.People object at 0x000001FD0725C908>
所以对应的需要的各个防范所需的参数值为:
SET第1个参数:是描述符类对象本身 SET第2个参数:是被描述符类对象 SET第3个参数:具体要设置的值(返回值可有可无)
GET第1个参数:是描述符类对象本身 GET第2个参数:是被描述符类对象 GET第3个参数:具体要返回的值(返回值可有可无)
DEL第1个参数:是描述符类对象本身 DEL第2个参数:是被描述符类对象的属性(无返回值)
修改被装饰器的对象,内部定义一个临时变量,用于接管我们的被描述类对象的属性。
PS 使用删除的时候,我们的描述类,必须有一个临时变量接管被描述的类的属性,
#!/usr/bin/evn python
# coding=utf-8
# 描述的类
class Describe:
def __init__(self):
self.key = '我是代理被描述类的属性的代理者'
def __get__(self, instance, value):
print('gET第1个参数:', self)
print('gET第2个参数:', instance)
print('gET第3个参数:', value)
value = '我是要返回的值:============'+self.key
return value
# def __set__(self, instance, value):
def __set__(self, instance, value):
pass
print('SET第1个参数:',self)
print('SET第2个参数:', instance)
print('SET第3个参数:', value)
self.key = '设置后---我是要返回的值:============' + value
return value
def __delete__(self,instance):
print("删除对象",instance)
del self.key
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
p = People()
print(p.name)
p.name = "A同学"
print(p.name)
# 删除属性
del p.name
# 删除后再调用
print(p.name)
上面的输出结果为:
gET第1个参数: <__main__.Describe object at 0x000001EED618CA20>
gET第2个参数: <__main__.People object at 0x000001EED618CAC8>
gET第3个参数: <class '__main__.People'>
我是要返回的值:============我是代理被描述类的属性的代理者
SET第1个参数: <__main__.Describe object at 0x000001EED618CA20>
SET第2个参数: <__main__.People object at 0x000001EED618CAC8>
SET第3个参数:A同学
gET第1个参数: <__main__.Describe object at 0x000001EED618CA20>
gET第2个参数: <__main__.People object at 0x000001EED618CAC8>
gET第3个参数: <class '__main__.People'>
我是要返回的值:============设置后---我是要返回的值:============A同学
删除对象 <__main__.People object at 0x000001EED618CAC8>
gET第1个参数: <__main__.Describe object at 0x000001EED618CA20>
gET第2个参数: <__main__.People object at 0x000001EED618CAC8>
gET第3个参数: <class '__main__.People'>
Traceback (most recent call last):
File "D:/code/python/local_python/AntFlask/设计模式/描述符.py", line 45, in <module>
print(p.name)
File "D:/code/python/local_python/AntFlask/设计模式/描述符.py", line 15, in __get__
value = '我是要返回的值:============'+self.key
AttributeError: 'Describe' object has no attribute 'key'
错误的原因是我们已经删除了对应我的属性了,还去调用这个属性的时候,就找不到了!
被描述的属性的查找优先级:
类型1:【数据描述符】示例:
# 描述的类
class Describe:
def __init__(self):
self.key = '我是代理被描述类的属性的代理者'
def __get__(self, instance, value):
print("返回属性", instance,value)
return self.key
# def __set__(self, instance, value):
def __set__(self, instance, value):
print("设置属性", instance,value)
return value
def __delete__(self,instance):
print("删除对象",instance)
del self.key
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
def __init__(self):
# 这里引发了设置属性,但是调用的描述类的属性值,也就是说,是调用了类属性的值,而不是给对象建立一个实例属性
self.name = "上面什么啊"
self.sex= "男"
p = People()
# 它调用的不是对象的 self.name的属性,调用的是类的属性
print(p.name)
print(p.sex)
输出的结果:
设置属性 <__main__.People object at 0x00000207896DC908> 上面什么啊
返回属性 <__main__.People object at 0x00000207896DC908> <class '__main__.People'>
我是代理被描述类的属性的代理者
男
分析:
1:self.name = "上面什么啊" 它引发了是类属性的调用,然后它被描述类代理了,所以触发了 set()方法并传了上面什么啊这个值进去。
2:self.sex= "男" 没有被代理,所以它是给对象创建了一个实例的对象。
类型2:【非数据描述符】示例:
#!/usr/bin/evn python
# coding=utf-8
# 描述的类
class Describe:
def __init__(self):
self.key = '我是代理被描述类的属性的代理者'
def __get__(self, instance, value):
print("返回属性", instance,value)
return self.key
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
def __init__(self):
# 这里引发了设置属性,但是调用的描述类的属性值,也就是说,是调用了类属性的值,而不是给对象建立一个实例属性
self.name = "上面什么啊"
self.sex= "男"
p = People()
# 它调用的不是对象的 self.name的属性,调用的是类的属性
print(p.name)
print(p.sex)
输出结果为:
上面什么啊
男
总结
使用了描述符以后,属性查找优先级会改变:
数据描述符(包含了set和delete、get方法)查找属性优先级:
类属性>数据描述符>实例属性>非数据描述符(其实是方法调用顺序)
非数据描述符(只包含get方法的)查找属性优先级:
实例属性>类属性>非数据描述符__get__方法(其实是方法调用顺序)
:意思就是:如果没有实例属性,去找类的属性(修饰符类里面定义的临时变量),如果还没有就是再去直接低调用__get__方法
描述符失效场景:
如果描述符作为类变量时,使用类调用类变量(即描述符被当做类变量调用)
示例如:
#!/usr/bin/evn python
# coding=utf-8
# 描述的类
class Describe:
def __init__(self):
self.key = '我是代理被描述类的属性的代理者'
def __get__(self, instance, value):
print("返回属性", instance, value)
return self.key
# def __set__(self, instance, value):
def __set__(self, instance, value):
print("设置属性", instance, value)
return value
def __delete__(self, instance):
print("删除对象", instance)
del self.key
# 被描述的类
class People:
# 注意这个了类变量==把一个描述类的对象给赋值到被描述的类的属性的变量上
name = Describe()
def __init__(self):
# 这里引发了设置属性,但是调用的描述类的属性值,也就是说,是调用了类属性的值,而不是给对象建立一个实例属性
self.name = "上面什么啊"
self.sex = "男"
p = People()
People.name = "sadsfd"
print(People.name)
print(p.name)
输出结果为:
设置属性 <__main__.People object at 0x000002892663CB00> 上面什么啊
sadsfd
sadsfd
分析:
当上面使用People.name = "sadsfd"之后,我们的描述符后续的调用都失效了!不会再被当做描述符。
总描述使用原则:
用来代理另外一个类的属性的,必须把描述符定义成类的属性,且不能定义到构造函数中。 描述符不要使用类本身去设置相关值,否则描述符会失效,它需通过实例化的对象来调用。 描述符需要被实例化对象调用才算生效
个人其他博客地址
简书:https://www.jianshu.com/u/d6960089b087
掘金:https://juejin.cn/user/2963939079225608
小钟同学 | 文 【原创】| QQ:308711822
1:本文相关描述主要是个人的认知和见解,如有不当之处,还望各位大佬指正。 2:关于文章内容,有部分内容参考自互联网,如有链接会声明标注;如没有及时标注备注的链接的,如有侵权请联系,我会立即删除处理哟。




