Mercurial > MadButterfly
comparison pyink/trait.py @ 1345:e0400a2b7c35
Use trait instead of mixin for component_manager
author | Thinker K.F. Li <thinker@codemud.net> |
---|---|
date | Fri, 11 Feb 2011 15:10:37 +0800 |
parents | |
children | 4fdc998d3dc3 |
comparison
equal
deleted
inserted
replaced
1344:8f1f8ef5c9ea | 1345:e0400a2b7c35 |
---|---|
1 ## \brief Implement descriptors for mapping attributes for traits. | |
2 # | |
3 # The instances of require map attributes of traits to corresponding | |
4 # attributes of the instance of the composition class. | |
5 # | |
6 class require(object): | |
7 def __get__(self, instance, owner): | |
8 if not instance: # from a class object | |
9 return self | |
10 | |
11 attrname = instance._trait_attrname_map[self] | |
12 composite_obj = instance._trait_composite_obj | |
13 val = getattr(composite_obj, attrname) | |
14 return val | |
15 | |
16 def __set__(self, instance, value): | |
17 attrname = instance._trait_attrname_map[self] | |
18 composite_obj = instance._trait_composite_obj | |
19 setattr(composite_obj, attrname, value) | |
20 pass | |
21 pass | |
22 | |
23 | |
24 ## \brief Decorator for making a class being a trait. | |
25 # | |
26 def trait(trait_clazz): | |
27 attrname_map = {} | |
28 trait_clazz._trait_attrname_map = attrname_map | |
29 | |
30 for attr in dir(trait_clazz): | |
31 value = getattr(trait_clazz, attr) | |
32 if value != require: | |
33 continue | |
34 | |
35 require_o = require() | |
36 setattr(trait_clazz, attr, require_o) | |
37 attrname_map[require_o] = attr | |
38 pass | |
39 | |
40 trait_clazz._is_trait = True | |
41 | |
42 return trait_clazz | |
43 | |
44 | |
45 ## \brief The function to return a proxy for a method of a trait. | |
46 # | |
47 def trait_method_proxy(trait_clazz, method): | |
48 def trait_method_proxy_real(self, *args, **kws): | |
49 if not hasattr(self, '_all_trait_objs'): | |
50 # self is an instance of the class composed from traits. | |
51 self._all_trait_objs = {} | |
52 pass | |
53 | |
54 try: | |
55 trait_obj = self._all_trait_objs[trait_clazz] | |
56 except KeyError: | |
57 trait_obj = trait_clazz() | |
58 trait_obj._trait_composite_obj = self | |
59 self._all_trait_objs[trait_clazz] = trait_obj | |
60 pass | |
61 | |
62 r = method(trait_obj, *args, **kws) | |
63 | |
64 return r | |
65 | |
66 return trait_method_proxy_real | |
67 | |
68 | |
69 ## \brief Derive and modify an existing trait. | |
70 # | |
71 def derive_trait(a_trait, composite_clazz): | |
72 attrname_map = None | |
73 if hasattr(composite_clazz, 'provide_traits'): | |
74 provide_traits = composite_clazz.provide_traits | |
75 if a_trait in provide_traits: | |
76 provide_trait = provide_traits[a_trait] | |
77 attrname_map = dict(a_trait._trait_attrname_map) | |
78 attrname_map.update(provide_trait) | |
79 pass | |
80 pass | |
81 | |
82 dic = {} | |
83 if attrname_map: | |
84 dic['_trait_attrname_map'] = attrname_map | |
85 pass | |
86 | |
87 derived = type('derived_trait', (a_trait,), dic) | |
88 | |
89 return derived | |
90 | |
91 ## \brief A decorator to make class composited from traits. | |
92 # | |
93 # The class decorated by composite must own a use_traits attribute. | |
94 # | |
95 # \verbatim | |
96 # @trait | |
97 # class trait_a(object): | |
98 # var_a = require | |
99 # def xxx(self): return self.var_a | |
100 # | |
101 # @trait | |
102 # class trait_b(object): | |
103 # def ooo(self): pass | |
104 # | |
105 # @composite | |
106 # class foo(object): | |
107 # use_traits = (trait_a, trait_b) | |
108 # | |
109 # var_a = 'value of var_a' | |
110 # pass | |
111 # | |
112 # obj = foo() | |
113 # \endverbatim | |
114 # | |
115 # To make a class from a set of traits. You must decorate the class | |
116 # with the decorator 'composite'. The class must has an attribute, | |
117 # named use_traits, to provide a list or tuple of traits. | |
118 # | |
119 # Class that defines a trait must decorated with the decorator | |
120 # 'trait'. If the trait need to access state (varaibles) of the | |
121 # intances of composition class, it must define attributes with value | |
122 # 'require', likes what 'var_a' of trait_a does. Then, the attributes | |
123 # would be mapped to corresponding attributes of instances of | |
124 # composition class. For example, when you call obj.xxx(), it returns | |
125 # value of 'var_a', and attribute 'var_a' is a property that returns | |
126 # the value of 'var_a' of 'obj', an instance of class foo. | |
127 # | |
128 # By default, traits map attribute 'var_a' to 'var_a' of instances of | |
129 # composition classes. But, you can change it by specifying the map | |
130 # in an attribute, named 'provide_traits', defined in composition | |
131 # class. The attribute provide_traits is a dictionary mapping from | |
132 # trait class to a dictionary, named 'attrname_map' for the trait. | |
133 # The attrname_map maps require attributes of the trait to names of | |
134 # attributes of instances of the composition class. | |
135 # | |
136 def composite(clazz): | |
137 if not hasattr(clazz, 'use_traits'): | |
138 raise KeyError, \ | |
139 '%s has no use_trait: it must be a list of traits' % (repr(clazz)) | |
140 traits = clazz.use_traits | |
141 | |
142 for a_trait in traits: | |
143 if not hasattr(a_trait, '_is_trait'): | |
144 raise TypeError, '%s is not a trait' % (repr(a_trait)) | |
145 pass | |
146 | |
147 # | |
148 # Check content of clazz.provide_traits | |
149 # | |
150 if hasattr(clazz, 'provide_traits'): | |
151 if not isinstance(clazz.provide_traits, dict): | |
152 raise TypeError, \ | |
153 'provide_traits of a composite must be a dictionary' | |
154 | |
155 provide_set = set(clazz.provide_traits.keys()) | |
156 trait_set = set(traits) | |
157 unused_set = provide_set - trait_set | |
158 if unused_set: | |
159 raise ValueError, \ | |
160 'can not find %s in provide_traits' % (repr(unused_set.pop())) | |
161 | |
162 for trait, attrname_map in clazz.provide_traits.items(): | |
163 for req in attrname_map: | |
164 if not isinstance(req, require): | |
165 raise TypeError, \ | |
166 '%s is not a require: key of an ' \ | |
167 'attribute name map must be a require' % (repr(req)) | |
168 pass | |
169 pass | |
170 pass | |
171 | |
172 # | |
173 # Count number of appearing in all traits for every attribute name. | |
174 # | |
175 attrname_cnts = {} | |
176 for a_trait in traits: | |
177 for attr in dir(a_trait): | |
178 if attr.startswith('_'): | |
179 continue | |
180 | |
181 value = getattr(a_trait, attr) | |
182 if value == require: | |
183 continue | |
184 | |
185 attrname_cnts[attr] = attrname_cnts.setdefault(attr, 0) + 1 | |
186 pass | |
187 pass | |
188 | |
189 if hasattr(clazz, 'method_map_traits'): | |
190 method_map_traits = clazz.method_map_traits | |
191 else: | |
192 method_map_traits = {} | |
193 pass | |
194 | |
195 # | |
196 # Set a proxy for every exported methods. | |
197 # | |
198 derived_traits = clazz._derived_traits = {} | |
199 for a_trait in traits: | |
200 derived = derive_trait(a_trait, clazz) | |
201 derived_traits[a_trait] = derived | |
202 | |
203 if a_trait in method_map_traits: | |
204 method_map_trait = method_map_traits[a_trait] | |
205 else: | |
206 method_map_trait = {} | |
207 | |
208 for attr in dir(derived): | |
209 if attr not in attrname_cnts: # hidden | |
210 continue | |
211 if attrname_cnts[attr] > 1: # conflict | |
212 continue | |
213 | |
214 if hasattr(clazz, attr): # override | |
215 continue | |
216 | |
217 value = getattr(a_trait, attr) | |
218 if value in method_map_trait: # do it later | |
219 continue | |
220 | |
221 value = getattr(derived, attr) | |
222 | |
223 if not callable(value): | |
224 raise TypeError, \ | |
225 '%s.%s is not a callable' % (repr(a_trait), attr) | |
226 | |
227 func = value.im_func | |
228 proxy = trait_method_proxy(derived, func) | |
229 setattr(clazz, attr, proxy) | |
230 pass | |
231 pass | |
232 | |
233 # | |
234 # Map methods specified in method_map_traits. | |
235 # | |
236 for a_trait, method_map_trait in method_map_traits.items(): | |
237 if a_trait not in derived_traits: | |
238 raise TypeError, \ | |
239 '%s is not a trait used by the composition class' % \ | |
240 (repr(a_trait)) | |
241 | |
242 derived = derived_traits[a_trait] | |
243 for method, attrname in method_map_trait.items(): | |
244 if not callable(method): | |
245 raise TypeError, \ | |
246 '%s.%s is not a callable' % (repr(a_trait), repr(method)) | |
247 func = method.im_func | |
248 proxy = trait_method_proxy(derived, func) | |
249 setattr(clazz, attrname, proxy) | |
250 pass | |
251 pass | |
252 | |
253 return clazz | |
254 | |
255 | |
256 if __name__ == '__main__': | |
257 @trait | |
258 class hello(object): | |
259 msg = require | |
260 | |
261 def hello(self, name): | |
262 return self.msg + ' hello ' + name | |
263 pass | |
264 | |
265 @trait | |
266 class bye(object): | |
267 msg = require | |
268 | |
269 def bye(self, name): | |
270 return self.msg + ' bye ' + name | |
271 pass | |
272 | |
273 @composite | |
274 class hello_bye(object): | |
275 use_traits = (hello, bye) | |
276 | |
277 provide_hello = {hello.msg: 'msg1'} | |
278 provide_traits = {hello: provide_hello} | |
279 | |
280 method_map_hello = {hello.hello: 'hello1'} | |
281 method_map_traits = {hello: method_map_hello} | |
282 | |
283 msg = 'hello_bye' | |
284 msg1 = 'hello_bye_msg1' | |
285 pass | |
286 | |
287 o = hello_bye() | |
288 assert o.hello1('Miky') == 'hello_bye_msg1 hello Miky' | |
289 assert o.bye('Miky') == 'hello_bye bye Miky' | |
290 print 'OK' | |
291 pass |