Mercurial > MadButterfly
view 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 |
line wrap: on
line source
## \brief Implement descriptors for mapping attributes for traits. # # The instances of require map attributes of traits to corresponding # attributes of the instance of the composition class. # class require(object): def __get__(self, instance, owner): if not instance: # from a class object return self attrname = instance._trait_attrname_map[self] composite_obj = instance._trait_composite_obj val = getattr(composite_obj, attrname) return val def __set__(self, instance, value): attrname = instance._trait_attrname_map[self] composite_obj = instance._trait_composite_obj setattr(composite_obj, attrname, value) pass pass ## \brief Decorator for making a class being a trait. # def trait(trait_clazz): attrname_map = {} trait_clazz._trait_attrname_map = attrname_map for attr in dir(trait_clazz): value = getattr(trait_clazz, attr) if value != require: continue require_o = require() setattr(trait_clazz, attr, require_o) attrname_map[require_o] = attr pass trait_clazz._is_trait = True return trait_clazz ## \brief The function to return a proxy for a method of a trait. # def trait_method_proxy(trait_clazz, method): def trait_method_proxy_real(self, *args, **kws): if not hasattr(self, '_all_trait_objs'): # self is an instance of the class composed from traits. self._all_trait_objs = {} pass try: trait_obj = self._all_trait_objs[trait_clazz] except KeyError: trait_obj = trait_clazz() trait_obj._trait_composite_obj = self self._all_trait_objs[trait_clazz] = trait_obj pass r = method(trait_obj, *args, **kws) return r return trait_method_proxy_real ## \brief Derive and modify an existing trait. # def derive_trait(a_trait, composite_clazz): attrname_map = None if hasattr(composite_clazz, 'provide_traits'): provide_traits = composite_clazz.provide_traits if a_trait in provide_traits: provide_trait = provide_traits[a_trait] attrname_map = dict(a_trait._trait_attrname_map) attrname_map.update(provide_trait) pass pass dic = {} if attrname_map: dic['_trait_attrname_map'] = attrname_map pass derived = type('derived_trait', (a_trait,), dic) return derived ## \brief A decorator to make class composited from traits. # # The class decorated by composite must own a use_traits attribute. # # \verbatim # @trait # class trait_a(object): # var_a = require # def xxx(self): return self.var_a # # @trait # class trait_b(object): # def ooo(self): pass # # @composite # class foo(object): # use_traits = (trait_a, trait_b) # # var_a = 'value of var_a' # pass # # obj = foo() # \endverbatim # # To make a class from a set of traits. You must decorate the class # with the decorator 'composite'. The class must has an attribute, # named use_traits, to provide a list or tuple of traits. # # Class that defines a trait must decorated with the decorator # 'trait'. If the trait need to access state (varaibles) of the # intances of composition class, it must define attributes with value # 'require', likes what 'var_a' of trait_a does. Then, the attributes # would be mapped to corresponding attributes of instances of # composition class. For example, when you call obj.xxx(), it returns # value of 'var_a', and attribute 'var_a' is a property that returns # the value of 'var_a' of 'obj', an instance of class foo. # # By default, traits map attribute 'var_a' to 'var_a' of instances of # composition classes. But, you can change it by specifying the map # in an attribute, named 'provide_traits', defined in composition # class. The attribute provide_traits is a dictionary mapping from # trait class to a dictionary, named 'attrname_map' for the trait. # The attrname_map maps require attributes of the trait to names of # attributes of instances of the composition class. # def composite(clazz): if not hasattr(clazz, 'use_traits'): raise KeyError, \ '%s has no use_trait: it must be a list of traits' % (repr(clazz)) traits = clazz.use_traits for a_trait in traits: if not hasattr(a_trait, '_is_trait'): raise TypeError, '%s is not a trait' % (repr(a_trait)) pass # # Check content of clazz.provide_traits # if hasattr(clazz, 'provide_traits'): if not isinstance(clazz.provide_traits, dict): raise TypeError, \ 'provide_traits of a composite must be a dictionary' provide_set = set(clazz.provide_traits.keys()) trait_set = set(traits) unused_set = provide_set - trait_set if unused_set: raise ValueError, \ 'can not find %s in provide_traits' % (repr(unused_set.pop())) for trait, attrname_map in clazz.provide_traits.items(): for req in attrname_map: if not isinstance(req, require): raise TypeError, \ '%s is not a require: key of an ' \ 'attribute name map must be a require' % (repr(req)) pass pass pass # # Count number of appearing in all traits for every attribute name. # attrname_cnts = {} for a_trait in traits: for attr in dir(a_trait): if attr.startswith('_'): continue value = getattr(a_trait, attr) if value == require: continue attrname_cnts[attr] = attrname_cnts.setdefault(attr, 0) + 1 pass pass if hasattr(clazz, 'method_map_traits'): method_map_traits = clazz.method_map_traits else: method_map_traits = {} pass # # Set a proxy for every exported methods. # derived_traits = clazz._derived_traits = {} for a_trait in traits: derived = derive_trait(a_trait, clazz) derived_traits[a_trait] = derived if a_trait in method_map_traits: method_map_trait = method_map_traits[a_trait] else: method_map_trait = {} for attr in dir(derived): if attr not in attrname_cnts: # hidden continue if attrname_cnts[attr] > 1: # conflict continue if hasattr(clazz, attr): # override continue value = getattr(a_trait, attr) if value in method_map_trait: # do it later continue value = getattr(derived, attr) if not callable(value): raise TypeError, \ '%s.%s is not a callable' % (repr(a_trait), attr) func = value.im_func proxy = trait_method_proxy(derived, func) setattr(clazz, attr, proxy) pass pass # # Map methods specified in method_map_traits. # for a_trait, method_map_trait in method_map_traits.items(): if a_trait not in derived_traits: raise TypeError, \ '%s is not a trait used by the composition class' % \ (repr(a_trait)) derived = derived_traits[a_trait] for method, attrname in method_map_trait.items(): if not callable(method): raise TypeError, \ '%s.%s is not a callable' % (repr(a_trait), repr(method)) func = method.im_func proxy = trait_method_proxy(derived, func) setattr(clazz, attrname, proxy) pass pass return clazz if __name__ == '__main__': @trait class hello(object): msg = require def hello(self, name): return self.msg + ' hello ' + name pass @trait class bye(object): msg = require def bye(self, name): return self.msg + ' bye ' + name pass @composite class hello_bye(object): use_traits = (hello, bye) provide_hello = {hello.msg: 'msg1'} provide_traits = {hello: provide_hello} method_map_hello = {hello.hello: 'hello1'} method_map_traits = {hello: method_map_hello} msg = 'hello_bye' msg1 = 'hello_bye_msg1' pass o = hello_bye() assert o.hello1('Miky') == 'hello_bye_msg1 hello Miky' assert o.bye('Miky') == 'hello_bye bye Miky' print 'OK' pass