1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
__all__ = ['include', 'MetaTOS']
import inspect, types, warnings
class OverridingError(NameError):
pass
class OverridingWarning(Warning):
pass
class Super(object):
# this is needed to fix a shortcoming of unbound super objects,
# i.e. this is how the unbound version of super should work
def __init__(self, thisclass):
self.__thisclass__ = thisclass
def __get__(self, obj, objcls):
return super(self.__thisclass__, obj or objcls)
def oldstyle(*bases):
"Return True if there are no bases or all bases are old-style"
return not bases or set(map(type, bases)) == set([types.ClassType])
class Namespace(dict):
"A named dictionary containing the attribute of a class and its ancestors"
@classmethod
def from_cls(klass, cls):
if oldstyle(cls):
mro = inspect.getmro(cls)
else:
mro = cls.__mro__[:-1] # all except object
dic = merge(subc.__dict__ for subc in reversed(mro))
return klass(cls.__name__, dic)
def __init__(self, name, attrs):
self.__name__ = name
self.update(attrs)
def merge(dicts):
"""Merge a sequence of dictionaries. In case of name clashes,
the last dict in the sequence wins."""
dic = {}
for d in dicts:
dic.update(d)
return dic
class MetaTOS(type):
"The metaclass of the Trait Object System"
def __new__(mcl, name, bases, dic):
if len(bases) > 1:
raise TypeError(
'Multiple inheritance of bases %s is forbidden for TOS classes'
% str(bases))
elif oldstyle(*bases): # converts into new-style
bases += (object,)
cls = mcl.__super.__new__(mcl, name, bases, dic)
setattr(cls, '_%s__super' % name, Super(cls))
return cls
MetaTOS._MetaTOS__super = Super(MetaTOS)
def find_common_names(namespaces):
"Perform n*(n-1)/2 namespace overlapping checks on a set of n namespaces"
n = len(namespaces)
if n <= 1: return
names = map(set, namespaces)
for i in range(0, n):
for j in range(i+1, n):
ci, cj = namespaces[i], namespaces[j]
common = names[i] & names[j]
if common:
yield common, ci, cj
def check_overridden(namespaces, exclude=frozenset(), raise_='error'):
"Raise an OverridingError for common names not in the exclude set"
for common, n1, n2 in find_common_names(namespaces):
overridden = ', '.join(common - exclude)
if overridden:
msg = '%s overrides names in %s: {%s}' % (
n1.__name__, n2.__name__, overridden)
if raise_ == 'error':
raise OverridingError(msg)
elif raise_ == 'warning':
warnings.warn(msg, OverridingWarning, stacklevel=2)
known_metas = set([MetaTOS])
def get_right_meta(metatos, bases):
# there is only one base because of the single-inheritance constraint
try:
base = bases[0]
except IndexError:
base = object
meta = type(base)
if meta in (types.ClassType, type): # is a builtin meta
return metatos
elif any(issubclass(meta, m) for m in known_metas):
return meta
# meta is independent from all known_metas, make a new one with
# __new__ method coming from MetaTOS
newmeta = type(
'_TOS' + meta.__name__, (meta,), dict(__new__=metatos.__new__))
setattr(newmeta, '_%s__super' % metatos.__name__, Super(newmeta))
known_metas.add(newmeta)
return newmeta
exclude_attrs = set('__doc__ __module__ __dict__ __weakref__'.split())
def new(metatos, name, bases, attrs, traits):
# traits as in Squeak take the precedence over the base class
# but they are overridden by attributes in the class
namespaces = map(Namespace.from_cls, traits)
check_overridden(namespaces, exclude=set(attrs)|exclude_attrs)
meta = get_right_meta(metatos, bases)
cls = meta(name, bases, merge(namespaces + [Namespace(name, attrs)]))
cls.__traits__ = traits
for t in traits:
setattr(cls, '_%s__super' % t.__name__, Super(cls))
return cls
def include(*traits, **kw):
"Returns a class factory"
metatos = kw.get('MetaTOS', MetaTOS) # other kw free for future extensions
def makecls(name, bases, dic):
return new(metatos, name, bases, dic, traits)
makecls.__name__ = 'include_%s' % '_'.join(m.__name__ for m in traits)
return makecls
|