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__.