speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / noconflict.py
1 import inspect
2 import types
3
4 ############## preliminary: two utility functions #####################
5
6
7 def skip_redundant(iterable, skipset=None):
8 "Redundant items are repeated items or items in the original skipset."
9 if skipset is None:
10 skipset = set()
11 for item in iterable:
12 if item not in skipset:
13 skipset.add(item)
14 yield item
15
16
17 def remove_redundant(metaclasses):
18 skipset = set([type])
19 for meta in metaclasses: # determines the metaclasses to be skipped
20 skipset.update(inspect.getmro(meta)[1:])
21 return tuple(skip_redundant(metaclasses, skipset))
22
23 ##################################################################
24 ## now the core of the module: two mutually recursive functions ##
25 ##################################################################
26
27
28 memoized_metaclasses_map = {}
29
30
31 def get_noconflict_metaclass(bases, left_metas, right_metas):
32 """Not intended to be used outside of this module, unless you know
33 what you are doing."""
34 # make tuple of needed metaclasses in specified priority order
35 metas = left_metas + tuple(map(type, bases)) + right_metas
36 needed_metas = remove_redundant(metas)
37
38 # return existing confict-solving meta, if any
39 if needed_metas in memoized_metaclasses_map:
40 return memoized_metaclasses_map[needed_metas]
41 # nope: compute, memoize and return needed conflict-solving meta
42 elif not needed_metas: # wee, a trivial case, happy us
43 meta = type
44 elif len(needed_metas) == 1: # another trivial case
45 meta = needed_metas[0]
46 # check for recursion, can happen i.e. for Zope ExtensionClasses
47 elif needed_metas == bases:
48 raise TypeError("Incompatible root metatypes", needed_metas)
49 else: # gotta work ...
50 metaname = '_' + ''.join([m.__name__ for m in needed_metas])
51 meta = classmaker()(metaname, needed_metas, {})
52 memoized_metaclasses_map[needed_metas] = meta
53 return meta
54
55
56 def classmaker(left_metas=(), right_metas=()):
57 def make_class(name, bases, adict):
58 print("make_class", name)
59 metaclass = get_noconflict_metaclass(bases, left_metas, right_metas)
60 return metaclass(name, bases, adict)
61 return make_class