Traits implementation for Python 3

E

Ethan Furman

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top