Click to Visit Homepage : zzyzz.top


Descriptor - Python 描述符协议

  1 描述符(descriptor)
  2    descriptor 是一个实现了 __get____set____delete__ 特殊方法中的一个或多个的.
  3     
  4     与 descriptor 有关的几个名词解释,
  5         描述符类(descriptor class)
  6             实现描述符协议的类,被称作'描述符类'.
  7     
  8         托管类(managed class)
  9           把描述符实例声明为类属性的类,被称作'托管类'.
 10         
 11         描述符实例(descriptor instance)
 12           描述符类的各个实例,声明为托管类的类属性.
 13     
 14         托管实例(managed instance)
 15           托管类的实例。
 16 
 17         储存属性(storage attribute)
 18           托管实例中存储自身托管属性的属性,被称作'储存属性'.这种属性与描述符属性不同,描述符属性都是类属性(self.X)
 19         
 20         托管属性(managed attribute)
 21             托管类中由描述符实例处理的公开属性,值存储在储存属性中。
 22             也就是说,描述符实例和储存属性为托管属性建立了基础,这一点一定要理解.
 23     
 24     *** 描述符的用法是,创建一个实例,作为另一个类的类属性(其实例作为另一个类-'托管类'的类属性).
 25         描述符管理'托管类''托管属性'的存取和删除,数据通常存储在'托管实例'中.
 26     
 27     
 28     描述符协议涉及的特殊方法,
 29         __get__(self, instance, owner),
 30             获取 'owner' class'属性'(class attribute access) 或 '实例'(instance attribute access)
 31             'owner' 始终是 'attribute''instance' 的 owner class(即托管类); 'instance' 的值为'托管实例', 
 32             或者为 None 在不是通过'托管实例'调用的情况下(托管类直接调用'类属性').
 33             该方法返回描述符所管理相应 'attribute' 或丢出 'AttributeError exception'.
 34          
 35         __set__(self, instance, value),
 36             设置托管类的 'instance' 的属性为 'value'
 37             
 38         __delete__(self, instance),
 39             删除托管类的 'instance' 的属性
 40         
 41         __set_name__(self, owner, name)
 42             在托管类实例别创建的时候调用, 用来设置描述符的名字 'name'  (__dict__ key)
 43             
 44             注, 以上对特殊方法的描述中, 'attribute' 指的是托管类的 '__dict__' 的属性(name / key in __dict__ of owner class)
 45 
 46     
 47     
 48     描述符分类,
 49         Python 存取属性的方式是非'对称'的。通过实例读取属性时,通常返回的是实例中定义的属性;
 50         但是,如果实例中没有指定的属性,那么会获取类属性。
 51         而为实例中的属性赋值时,如指定属性不存在通常会在实例中创建属性,根本不影响类属性。
 52         这种不'对称'的处理方式对描述符也有影响。根据是否定义 __set__ 方法,描述符可分为两大类。
 53         
 54         覆盖型描述符,
 55             实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。
 56             (特性 'property' 是覆盖型描述符:如果没提供 __set__ 方法, property 类中的 __set__ 方法会抛出 AttributeError 异常,指明那个属性是只读的。)
 57             例子,
 58                 class Override(object):                   # 覆盖性描述符
 59                     def __set__(self, instance, value): 
 60                         print('Override - SET - args :',(self,instance,value))
 61                 
 62                     def __get__(self, instance, owner):
 63                         print('Override - GET - args :', (self, instance, owner))
 64                                 
 65                 class Override_Noget(object):             # 没有实现 __get__ 方法的覆盖性描述符
 66                     def __set__(self, instance, value):
 67                         print('Override_Noget - SET - args :', (self, instance, value))
 68                         return
 69                 
 70                 class NonOverride(object):                # 非覆盖性描述符
 71                     def __get__(self, instance, owner):
 72                         print('NonOverride - GET - args :', (self, instance, owner))
 73                 
 74                 class Registor(object):                   # 覆盖与非覆盖描述符示例
 75                     Override = Override()
 76                     Override_Noget = Override_Noget()
 77                     #NonOverride = NonOverride()
 78                 
 79                 if __name__ == '__main__':
 80                         print('*'*8 + ' Override ' + '*'*8)
 81                         regist6 = Registor()                                          
 82                         regist7 = Registor()
 83                         
 84                         print(vars(regist6))       
 85                         regist6.Override                       
 86                         Registor.Override                      #1
 87                         regist6.Override = 789                 #2
 88                         print(vars(regist6))
 89                         regist6.Override                       #3
 90                         regist6.__dict__['Override'] = 911     #4 绕过描述赋, 向 __dict__ 字典赋值
 91                         print(vars(regist6))                   #4
 92                         print(regist6.Override)
 93                         print(regist6.__dict__['Override'])
 94                         
 95                         print('*' * 8 + ' Override_Noget ' + '*' * 8)
 96                         print(vars(regist7))
 97                         print(regist7.Override_Noget)          #5
 98                         print(Registor.Override_Noget)         #5
 99                     
100                         regist7.Override_Noget = 'WOW'         #6
101                         print(regist7.Override_Noget)
102                     
103                         regist7.__dict__['Override_Noget'] = 'sos'   #7 通过实例的 __dict__ 属性设置名为 Override_Noget 的实例属性
104                         print(vars(regist7))
105                         print(regist7.Override_Noget)                #7
106                     
107                         regist7.Override_Noget = 'MOM'
108                         print(regist7.Override_Noget)                #8
109                         print(vars(regist7))                       
110                 
111                 Output,                   
112                     # ******** Override ********
113                     # {}
114                     # Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
115                     # Override - GET - args : (<__main__.Override object at 0x03A83C10>, None, <class '__main__.Registor'>)
116                             #1 通过 Registor 类直接调用 '类属性', 触发 __get__ 方法, 'instance' 参数是 'None'
117                     # Override - SET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, 789)
118                             #2 regist6.Override = 'female' 赋值, 触发 __set__ 方法, 参数 'value' 是 '789'
119                     # {}
120                     # Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
121                             #3 regist6.Override 取值,触发 __get__ 方法
122                     # {'Override': 911}
123                             #4 vars(regist6)
124                     # Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
125                     # None
126                     # 911
127                                            
128                     # ******** Override_Noget ********
129                     # {}
130                     # <__main__.Override_Noget object at 0x01B43C90>            
131                     # <__main__.Override_Noget object at 0x01B43C90>
132                             #5 由于没有实现 __get__ 方法,通过实例获取描述符会返回描述符本身
133                     # Override_Noget - SET - args : (<__main__.Override_Noget object at 0x01B43C90>, <__main__.Registor object at 0x01B43D10>, 'WOW')
134                             #6 regist7.Override_Noget 赋值,触发 __set__ 方法
135                     # <__main__.Override_Noget object at 0x01B43C90>
136                     # {'Override_Noget': 'sos'}
137                     # sos
138                             #7 Override_Noget 实例属性会'遮盖'描述符,但是只有读操作是如此(没有 __get__ 方法)
139                     # Override_Noget - SET - args : (<__main__.Override_Noget object at 0x01B43C90>, <__main__.Registor object at 0x01B43D10>, 'MOM')
140                     # {'Override_Noget': 'sos'}
141                     # sos
142                             #8 读取时,只要有同名的实例属性,描述符就会被遮盖                                                                                                                                                                            
143             
144         非覆盖型描述符
145             没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。
146             (方法是以非覆盖型描述符实现的)
147             例子,
148                 class NonOverride(object):                # 非覆盖性描述符
149                     def __get__(self, instance, owner):
150                         print('NonOverride - GET - args :', (self, instance, owner))
151                 
152                 class Registor(object):                   
153                     NonOverride = NonOverride()
154                     
155                 if __name__ == '__main__':
156                     regist8 = Registor()
157                     print('*' * 8 + ' PersonalInfo_NonOverride ' + '*' * 8)
158                     regist8.NonOverride             #1
159                 
160                     print(vars(regist8))
161                     regist8.NonOverride = '333'     #2 NonOverride 是非覆盖型描述符,因此实现 __set__ 方法
162                     print(vars(regist8))            #2
163                     print(regist8.NonOverride)      #2
164                 
165                                    
166                     del regist8.NonOverride
167                     Registor.NonOverride            #3
168                     print(vars(regist8))
169                     print(regist8.NonOverride)
170                 
171                 Output,
172                     # NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, <__main__.Registor object at 0x01143D90>, <class '__main__.Registor'>)
173                             #1 regist8.NonOverride 触发 __get__ 方法
174                     # {}
175                     # {'NonOverride': '333'}
176                     # 333
177                             #2 现有名为 NonOverride 的实例属性,把 Registor 类的同名描述符属性遮盖掉
178                     # NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, None, <class '__main__.Registor'>)
179                             #3 描述符依然存在
180                     # {}
181                     # NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, <__main__.Registor object at 0x01143D90>, <class '__main__.Registor'>)
182                     # None                 
183         
184         # *** *** 注, 描述符的 __set__ 方法实现了对'同名实例属性' '写' 的 '遮盖'; __get__ 方法 实现了对'同名实例属性' '读' 的 '遮盖'
185         
186           
187     描述符例子(描述符 相关逻辑比较复杂, 请耐心阅读示例),
188         例子背景是,一款有关锻炼的 APP 中有这样一个类,它用来记录个人锻炼数据,输入每天的锻炼数据后,给处针对性的锻炼建议.
189         这个类被创建的时候需要用户注册基本的个人信息(身高,年龄,性别). 考虑到实际应用, 参数 '身高','年龄'需要是非负数,
190         '性别''Male' 或者 'Female', 其他的值对这个3个参数来说都是非法值.
191 
192         class PersonalInfo_Override(object):
193         
194             def __init__(self,elment):
195                 self.elment = elment
196         
197             def __set__(self, instance, value):
198                 print('SET - args :',(self,instance,value))
199                 if isinstance(value,int):
200                     if value > 0:
201                         instance.__dict__[self.elment] = value          # 直接从instance.__dict__ 中处理,是为了跳过'特性',防止无限递归
202                         # setattr(instance,self.elment,value)           # 会引起无限递归
203                     else:
204                         raise ValueError('Invalid value setting - \'age\' and \'height\' must INT type with the value > 0')
205                 elif isinstance(value,str):
206                     if value.lower() == 'male' or value.upper() == 'FEMALE':
207                         instance.__dict__[self.elment] = value
208                     else:
209                         raise ValueError('Invalid value setting - \'SEX\' must STR type - \'male\' or \'FEMALE\'')
210                 else:
211                     raise ValueError('Invalid value setting - unsupported type - \'INT\' or \'STR\' type is prefer')
212         
213             def __get__(self, instance, owner):
214                 print('GET - args :', (self, instance, owner))
215                 if instance is None:
216                     return self
217                 else:
218                     return instance.__dict__[self.elment]
219                 
220         class PersonalInfo_Override_Noget(object):
221         
222             def __init__(self, elment):
223                 self.elment = elment
224         
225             def __set__(self, instance, value):
226                 if isinstance(value,int):
227                     if value > 0:
228                         instance.__dict__[self.elment] = value
229                     else:
230                         raise ValueError('Invalid value setting - \'age\' and \'height\' must INT type with the value > 0')
231                 elif isinstance(value,str):
232                     if value.lower() == 'male' or value.upper() == 'FEMALE':
233                         instance.__dict__[self.elment] = value
234                     else:
235                         raise ValueError('Invalid value setting - \'SEX\' must STR type - \'male\' or \'FEMALE\'')
236                 else:
237                     raise ValueError('Invalid value setting - unsupported type - \'INT\' or \'STR\' type is prefer')
238         
239         class PersonalInfo_NonOverride(object):
240         
241             def __init__(self, elment):
242                 self.elment = elment
243         
244             def __get__(self, instance, owner):
245                 if instance is None:
246                     return self
247                 else:
248                     return instance.__dict__[self.elment]
249         
250         class ExerciseRecord(object):
251             sex = PersonalInfo_Override('sex')
252             age = PersonalInfo_Override_Noget('age')
253             height = PersonalInfo_NonOverride('height')
254         
255             def __init__(self,sex,age,height):
256                 self.sex = sex
257                 self.age = age
258                 self.height = height
259         
260             def suggestion(self):
261                 pass
262             
263         Output,
264             if __name__ == '__main__':
265                 regist1 = ExerciseRecord('male',25,180)
266                 regist2 = ExerciseRecord('lala', 25, 180)      # ValueError: Invalid value setting - 'SEX' must STR type - 'male' or 'FEMALE'
267                 regist3 = ExerciseRecord('FEMALE', 0, 10)      # ValueError: Invalid value setting - 'age' and 'height' must INT type with the value > 0
268                 regist4 = ExerciseRecord('MALE', 2, -10)       # 'height' 属性应用的是'非覆盖描述符'(没有实现 __set__ 方法), 由于数据的有效性的验证是在 __set__ 方法中实现的,
269                                                                # 所以对于 'height' 属性来说并没有起到数据验证的作用
270                 regist5 = ExerciseRecord('male',-2,-5)         # ValueError: Invalid value setting - 'age' and 'height' must INT type with the value > 0
271             
272                 ExerciseRecord.sex                             # GET - args : (<__main__.PersonalInfo_Override object at 0x00FA3B50>, None, <class '__main__.ExerciseRecord'>)
273                                                                # 通过类直接调用 '类属性', 触发 __get__ 方法, 'instance' 参数是 'None'
274                        
275   
276     object.__get__(self, instance, owner)
277         Called to get the attribute of the 'owner' class (class attribute access) or of an instance of that class (instance attribute access).
278         'owner' is always the owner class, while 'instance' is the instance that the attribute was accessed through,
279         or 'None' when the attribute is accessed through the owner.
280         This method should return the (computed) attribute value or raise an AttributeError exception.
281 
282     object.__set__(self, instance, value)
283         Called to set the attribute on an instance 'instance' of the owner class to a new value, 'value'.
284 
285     object.__delete__(self, instance)
286         Called to delete the attribute on an instance 'instance' of the owner class.
287 
288     object.__set_name__(self, owner, name)
289         Called at the time the owning class owner is created. The descriptor has been assigned to name.
290     
291         *** Note,
292                  In the examples below, “the attribute” refers to the attribute whose name is the key of the property in the owner class__dict__.

 

posted @ 2017-09-19 19:24  zzYzz  阅读(350)  评论(0)    收藏  举报


Click to Visit Homepage : zzyzz.top