python - 数据描述符(class 内置 get/set/delete方法 )
数据描述符(class 内置 get/set/del方法 ):
# 什么是描述符 # 官方的定义:描述符是一种具有“捆绑行为”的对象属性。访问(获取、设置和删除)它的属性时,实际是调用特殊的方法(_get_(), # _set_(),_delete_())。也就是说,如果一个对象定义了这三种方法的任何一种,它就是一个描述符。 # 更多的理解: # 通常情况下,访问一个对象的搜索链是怎样的?比如a.x,首先,应该是查询 a._dict_[‘x’],然后是type(a)._dict_[‘x’],一直 # 向上知道元类那层止(不包括元类)。如果这个属性是一个描述符呢?那python就会“拦截”这个搜索链,取而代之调用描述符方法 # (_get_)。 # 描述符有什么作用? # The default behavior for attribute access is to get, set, or delete the attribute from an object's dictionary. # For instance, a.x has a lookup chain starting witha.__dict__[‘x'], then type(a).__dict__[‘x'], and continuing # through the base classes of type(a) excluding metaclasses. If the looked-up value is an object defining one of # the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. # Where this occurs in the precedence chain depends on which descriptor methods were defined. # —–摘自官方文档 # 简单的说描述符会改变一个属性的基本的获取、设置和删除方式。 # 数据描述符(data descriptor)和非数据描述符(non-data descriptors) # 数据描述符:定义了_set_ 和_get_方法的对象 # 非数据描述符:只定义了_get_方法的对象。通常方法都是非数据描述符。 # 区别: # 1、位于搜索链上的顺序。搜索链(或者优先链)的顺序大概是这样的:数据描述符>实体属性(存储在实体的_dict_中)>非数据描述符。 # 这个顺序初看起来挺晦涩。解释如下: # 获取一个属性的时候: # 首先,看这个属性是不是一个数据描述符,如果是,就直接执行描述符的_get_,并返回值。 # 其次,如果这个属性不是数据描述符,那就按常规去从_dict_里面取。 # 最后,如果_dict_里面还没有,但这是一个非数据描述符,则执行非数据描述符的_get_方法,并返回。 # 三个方法(协议): # • _get_(self, instance, owner) —获取属性时调用,返回设置的属性值,通常是_set_中的value,或者附加的其他组合值。 # • _set_(self, instance, value) — 设置属性时调用,返回None. # • _delete_(self, instance) — 删除属性时调用,返回None # 其中,instance是这个描述符属性所在的类的实体,而owner是描述符所在的类。 # 为什么要区分数据描述符和非数据描述符?(访问属性的优先级不一样) # 1.非数据描述符的获取属性的优先级链是,__getattribute__->找__dict__->找描述符,这条链的规则给了"缓存属性"理论支持(通常是这种) # 2.数据描述符,又分为: # 2.1 有__get__方法的覆盖型的获取属性的优先级链是,__getattribute__->找描述符,同时__set__方法也会覆盖对实例属性的赋值 # 操作,就是说任何外部对属性的赋值都将被__set__捕获,同时获取属性也是通过__get__方法获取,__dict__不再起直接作用。 # 2.2 没有__get__方法的覆盖型的获取属性优先级链是在没有对属性赋值时是__getattribute__->找描述符(返回描述符对象本身) # ,对属性赋值了之后是__getattribute__->找__dict__->找描述符 # 对象属性的访问顺序: # ①.实例属性 # ②.类属性 # ③.父类属性 # ④.__getattr__()方法 #数据描述符 - 优先级顺序: # ① __getattribute__(), 无条件调用 # ② 数据描述符:由 ① 触发调用 (若人为的重载了该 __getattribute__() 方法,可能会调职无法调用描述符) # ③ 实例对象的字典(若与描述符对象同名,会被覆盖哦) # ④ 类的字典 # ⑤ 非数据描述符 # ⑥ 父类的字典 # ⑦ __getattr__() 方法 # 描述符有什么用和好处 # 1)一般情况下不会用到,建议:先定基本的,以后真有需要再扩展。别贪玩。 # 2)可以在设置属性时,做些检测等方面的处理 # 3)缓存? # 4)设置属性不能被删除?那定义_delete_方法,并raise 异常。 # 5)还可以设置只读属性 # 6)把一个描述符作为某个对象的属性。这个属性要更改,比如增加判断,或变得更复杂的时候,所有的处理只要在描述符中操作就行了。 # #举例: # @property # #这就是一个数据描述符 # class Afff(): # pass # 更详尽的示例: #摘录于: #描述符阐述: # https://blog.csdn.net/allenalex/article/details/54097319 #应用示例: # https://www.jb51.net/article/91028.htm #优先级阐述: # https://www.cnblogs.com/Jimmy1988/p/6808237.html
示例:
#描述符类(相当于代理) #定义为了数据描述符:定义了_set_ 和_get_方法的对象 class Foo(): def __get__(self, instance, owner): print("执行Foo get方法") def __set__(self, instance, value): print("执行Foo set方法") def __delete__(self): print("执行Foo del方法") #主要运行的类: class Test(): #类的x属性被Foo代理,所以属性访问优先级也被修改: #类属性 > 数据描述符 > 实例属性 > 非实例属性 > __getattr__() x = Foo() def __init__(self,num): self.x = num #因为x类属性被Foo代理,触发Foo的set方法,而set方法只是打印了,没有做数据操作, abc = Test(100) abc.x print(abc.__dict__) # print输出:----------- # 执行Foo set方法 # 执行Foo get方法 # {} # 执行Foo del方法 # print输出:-----------
练习:
#描述符应用- 练习(判断录入类型是否合规) class Test(): def __init__(self, key,type_data): self.key = key self.type_data = type_data def __set__(self, instance, value): if isinstance(value,self.type_data): instance.__dict__[self.key] = value else: print("赋值类型错误") class People(): name =Test('name',str) def __init__(self,name,old): self.name = name self.old = old #赋值为str类型 name = 'anec' #赋值为int类型 name2 = 123 abc = People(name,23) abc2 = People(name2,24) print(abc.__dict__) print(abc2.__dict__)
既要脚踏实地,也需仰望天空