Traits implementation for Python 3

Ethan Furman ethan at stoneleaf.us
Fri Apr 2 23:12:21 EDT 2010


Well, it's been said than imitation is the sincerest form of flattery, 
so be flattered, Michele!

In order to gain a better understanding of the whole metaclass issue, I 
decided to make my own implementation, targeting Python 3.  I figured I 
could leave out a bunch of the complexity required to support Python 2.

The more I learn of 3, the more I love it.  Many improvements in 
simplicity and elegance.

At any rate, what I have is below.  My (very limited) initial tests are 
working fine.  super() appears to work as is.

Feedback appreciated!

~Ethan~

8<-----------------------------------------------------------------
"""Traits -- used instead of multiple inheritance, inspired my Michele
Simionato's Simple Traits experiment

Python Version: 3.x

Intended use:
     To combine a single base class, as many traits as needed/desired,
     glue code to combine together succussfully.  While traits are kept
     in classes, they should be functions only: no state information
     should be kept.  If different traits from different classes having
     the same name are combined into one one class, that class must
     specify which one it wants, or an error will be raised at class
     creation time.  If __magic_methods__ are part of the traits, their
     names must be specified in the trait classes __magic_traits__ list
     attribute; like-wise, if traits have required attributes that must
     be supplied by the composed class/other traits, their names must be
     in the __traits_required__ list attribute.

Name resolution order:
     Least - Base class
             Traits
     Most -  Current (composed) class"""

class Trait(type):
     def __init__(yo, *args, **kwargs):
         super().__init__(*args)
     def __new__(metacls, cls_name, cls_bases, cls_dict, traits=tuple()):
         if len(cls_bases) > 1:
             raise TypeError("multiple bases not allowed with Traits")
         result_class = type.__new__(metacls, cls_name, cls_bases, 

                                                              cls_dict)

         conflicts = False
         for trait in result_class.__trait_conflicts__:
             if getattr(result_class, trait, None) is None:
                 if not conflicts:
                     print()
                 conflicts = True
                 print("conflict found: %r is in %s" %
                         (trait,result_class.__trait_conflicts__[trait]))
         if conflicts:
             print()
             raise TypeError("conflicts must be resolved")
         delattr(result_class, '__trait_conflicts__')

         missing_required = False
         for trait in result_class.__required_traits__:
             if getattr(result_class, trait, None) is None:
                 if not missing_required:
                     print()
                 missing_required = True
                 print("missing requirement: %r from %s" %
                         (trait,result_class.__required_traits__[trait]))
         if missing_required:
             print()
             raise TypeError("requirements not met")
         delattr(result_class, '__required_traits__')

         return result_class

     @classmethod
     def __prepare__(metacls, name, bases, traits=tuple()):
         class _Dict(dict):
             "Normal dict with traits attribute"
         class _Traits:
             "container for trait bundles"
             def __repr__(yo):
                 return "(%s)" % ", ".join([str(getattr(yo, trait))
                         for trait in dir(yo)
                         if not trait.startswith('__')
                         or not trait.endswith('__')])
         if not traits:
             raise TypeError("no traits specified... what's the point?")
         elif type(traits) != tuple:
             traits = (traits, )
         class_dict = _Dict()
         # for direct . access here
         setattr(class_dict, 'traits', _Traits())
         # to survive proxification
         class_dict['traits'] = class_dict.traits
         setattr(class_dict, '__trait_conflicts__', dict())
         class_dict['__trait_conflicts__']=class_dict.__trait_conflicts__
         setattr(class_dict, '__magic_traits__', set())
         class_dict['__magic_traits__'] = class_dict.__magic_traits__
         setattr(class_dict, '__required_traits__', dict())
         class_dict['__required_traits__']=class_dict.__required_traits__
         for trait_bundle in traits:
             setattr(class_dict.traits,
                     trait_bundle.__name__,
                     trait_bundle)
         traits_dict = {}
         for trait_bundle in traits:
             metacls._integrate_traits(class_dict,
                                       traits_dict,
                                       trait_bundle)
         metacls._check_conflicts(class_dict, traits_dict)
         return class_dict

     @staticmethod
     def _check_conflicts(class_dict, traits_dict):
         for trait, cls_list in traits_dict.items():
             if len(cls_list) == 1:
                 class_dict[trait] = getattr(cls_list[0], trait)
             else:
                 first_trait = getattr(cls_list[0], trait)
                 for next_class in cls_list[1:]:
                     next_trait = getattr(next_class, trait)
                     if first_trait is not next_trait:
                         trouble = True
                         class_dict.__trait_conflicts__[trait] = cls_list
                         break
                     else:
                         class_dict[trait] = first_trait

     @staticmethod
     def _integrate_traits(class_dict, traits_dict, trait_bundle):
         magic_traits=getattr(trait_bundle, '__magic_traits__', tuple())
         for trait in magic_traits:
             class_dict.__magic_traits__.add(trait)
             if trait not in traits_dict:
                 traits_dict[trait] = [trait_bundle]
             else:
                 traits_dict[trait].append(trait_bundle)
         required = getattr(trait_bundle, '__required_traits__', tuple())
         for trait in required:
             #class_dict.__required_traits__.add(trait)
             if trait not in class_dict.__required_traits__:
                 class_dict.__required_traits__[trait] = [trait_bundle]
             else:
                 # may have to fix following line...
                 class_dict.__required_traits__[trait].\
                             append(trait_bundle)
         for trait in [t for t in dir(trait_bundle)
                 if not t.startswith('__') or not t.endswith('__')]:
             if trait not in traits_dict:
                 traits_dict[trait] = [trait_bundle]
             else:
                 traits_dict[trait].append(trait_bundle)

# test stuff
class TBundle1():
     def repeat(yo, text, count):
         return "%s " % text * count

class BaseClass():
     def repeat(yo, text, count):
         return "----%s----" % text * count
     def whatsit(yo, arg1):
         return "Got a %s!!" % arg1

class DerivedClass(BaseClass, metaclass=Trait, traits=TBundle1):
     def repeat(yo, text, count):
         print('whatever...')
     def whatsit(yo, arg1):
         print("calling baseclass's whatsit...")
         print(super().whatsit(arg1))

8< ------------------------------------------------------------------------

My apologies for the formatting.  With the exception of the one line 
above that may need to be recombined, it should compile (um, interpret? 
;) correctly as-is.



More information about the Python-list mailing list