Mercurial > MadButterfly
view pyink/trait.py @ 1457:416a18409603
Show an empty document in SVG viewer widget
author | Thinker K.F. Li <thinker@codemud.net> |
---|---|
date | Sat, 16 Apr 2011 22:25:35 +0800 |
parents | 1a4d15fe2c62 |
children | 28769a82a72d |
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 __init__(self, trait_clazz): self.trait_clazz = trait_clazz pass 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(trait_clazz) 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): # # Set a map mamping requires to attribute names. # # Search provide_traits for requires of a_trait, and patch # attrname_map for it. # attrname_map = None if hasattr(composite_clazz, 'provide_traits'): provide_traits = composite_clazz.provide_traits attrname_map = dict(a_trait._trait_attrname_map) for req in provide_traits: if req.trait_clazz == a_trait: attrname_map[req] = provide_traits[req] pass pass pass dic = {} if attrname_map: dic['_trait_attrname_map'] = attrname_map pass derived = type('derived_trait', (a_trait,), dic) return derived ## \brief Check explicity providing for requires. # # Composite maps require attributes of traits to the attribute, with # the same name, of composition class by default. But, composition # class can specify name of the attribute that will satisfy a require. # def _check_provide_traits(clazz): traits = clazz.use_traits # # 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([req.trait_clazz for req in 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 req in clazz.provide_traits: 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 ## \brief Include methods from trait for a composition class. # def _include_methods(clazz): traits = clazz.use_traits # # 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 isinstance(value, require) or not callable(value): 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 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_traits: # do it later continue value = getattr(derived, attr) if not callable(value): continue func = value.im_func proxy = trait_method_proxy(derived, func) setattr(clazz, attr, proxy) pass pass # # Set a proxy for methods specified in method_map_traits. # for method, attrname in method_map_traits.items(): if not callable(method): raise TypeError, \ '%s.%s is not a callable' % (repr(a_trait), repr(method)) a_trait = method.im_class 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] func = method.im_func proxy = trait_method_proxy(derived, func) setattr(clazz, attrname, proxy) pass pass ## \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 of a composition class is a # dictionary mapping from require attributes of used traits to names # of attributes of the composition class. # # \verbatim # @composite # class foo(object): # use_trait = (trait_a, trait_b) # provide_traits = {trait_a.var_a: 'var_foo'} # # var_foo = 'value of var_foo' # pass # \endverbatim # # Like mapping require attributes of used traits, there is a map, # named method_map_traits, for methods of used traits. # # \verbatim # @composite # class foo(object): # use_trait = (trait_a, trait_b) # provide_traits = {trait_a.var_a: 'var_foo'} # method_map_traits = {trait_a.xxx: 'hello') # # var_foo = 'value of var_foo' # pass # \endverbatim # # Previous example maps trait_a.xxx method to foo.hello method. # composite does not include methods that has a name prefixed by a '_' # charater. But, you can still force it, by an explicity mapping in # method_map_traits, to include a method prefixed by a '_' character. # 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_provide_traits(clazz) _include_methods(clazz) return clazz if __name__ == '__main__': @trait class hello(object): msg = require bye_provide = require def hello(self, name): return self.msg + ' hello ' + name def hello_provide(self): return 'hello provide' def require_bye(self): return self.bye_provide() pass @trait class bye(object): msg = require hello_provide = require def bye(self, name): return self.msg + ' bye ' + name def bye_provide(self): return 'bye provide' def require_hello(self): return self.hello_provide() pass @composite class hello_bye(object): use_traits = (hello, bye) provide_traits = {hello.msg: 'msg1'} method_map_traits = {hello.hello: 'hello1'} 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' assert o.require_bye() == 'bye provide' assert o.require_hello() == 'hello provide' print 'OK' pass