diff options
Diffstat (limited to 'pypers/recipes/noconflict_alex.py')
-rwxr-xr-x | pypers/recipes/noconflict_alex.py | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/pypers/recipes/noconflict_alex.py b/pypers/recipes/noconflict_alex.py new file mode 100755 index 0000000..542e086 --- /dev/null +++ b/pypers/recipes/noconflict_alex.py @@ -0,0 +1,95 @@ +# noconflict_alex.py + +import inspect, types +try: set +except NameError: + from sets import Set as set + +memoized_metaclasses_map = {} + +def uniques(sequence, skipset): + for item in sequence: + if item not in skipset: + skipset.add(item) + yield item# noconflict.py + +def remove_redundant(classes): + redundant = set([types.ClassType]) + for c in classes: + redundant.update(inspect.getmro(c)[1:]) + return tuple(uniques(classes, redundant)) + +def offending_metaclass_in(metas): + for m in metas: + if not issubclass(m, type): return True + return False + +def _get_noconflict_metaclass(bases, left_metas, right_metas): + # make tuple of needed metaclasses in specified priority order + metas = left_metas + tuple(map(type, bases)) + right_metas + needed_metas = remove_redundant(metas) + + # return existing confict-solving meta, if any + if needed_metas in memoized_metaclasses_map: + return memoized_metaclasses_map[needed_metas] + + # nope: compute, memoize and return needed conflict-solving meta + if not needed_metas: # wee, a trivial case, happy us + meta = type + elif len(needed_metas) == 1: # another trivial case + meta = needed_metas[0] + elif offending_metaclass_in(needed_metas): # es. Zope Extension Classes + raise TypeError("Incompatible root metatypes", needed_metas) + else: # gotta work ... + metaname = '_' + ''.join([m.__name__ for m in needed_metas]) + meta = classmaker()(metaname, needed_metas, {}) + memoized_metaclasses_map[needed_metas] = meta + return meta + +def classmaker(left_metas=(), right_metas=()): + def make_class(name, bases, adict): + metaclass = _get_noconflict_metaclass(bases, left_metas, right_metas) + return metaclass(name, bases, adict) + return make_class + +__test__ = dict( + ex1 = """ +>>> class Meta_A(type): pass +... +>>> class Meta_B(type): pass +... +>>> class A: __metaclass__ = Meta_A +... +>>> class B: __metaclass__ = Meta_B +... +>>> class C(A, B): pass +... +Traceback (most recent call last): + File "<stdin>", line 1, in ? +TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases + +>>> import __main__ as noconflict +>>> class Meta_C(Meta_A, Meta_B): pass +... +>>> class C(A, B): __metaclass__ = Meta_C +... + +>>> class C(A, B): __metaclass__ = noconflict.classmaker() +... +>>> class D(A): +... __metaclass__ = noconflict.classmaker((Meta_B,)) +""", + remove_redundant=""" + >>> class C1(object): pass + ... + + >>> class C2(object): pass + ... + >>> from __main__ import remove_redundant + >>> remove_redundant((C1, C2, C1)) + (<class '__main__.C1'>, <class '__main__.C2'>) +""") + +if __name__ == "__main__": + import doctest,__main__ + doctest.testmod(__main__) |