# HG changeset patch # User aurelien@trantor.local # Date 1272264366 -7200 # Mon Apr 26 08:46:06 2010 +0200 # Node ID ef5fcae868e0f5aeb3add482cdc191afc18ff335 # Parent 7f6d34babb7323f5b068e2b8c3fd2225d409ffe9 test mro, refactor next_method/call_next_method, method decorator accepts tuple as type specifier argument for multiple registration diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -2,14 +2,22 @@ from gf import dispatch as mm -def method(*types): +def method(*signatures): + return _method(_METHS, signatures) + +def _method(registry, signatures): """ decorator to globally register a method in a gf - named like the method itself""" + named like the method itself """ def _wrap(meth): name = meth.__name__ - gf = _METHS.get(name) + gf = registry.get(name) if gf is None: - gf = _METHS[name] = mm.GF() - gf.register(types, meth) + gf = registry[name] = mm.GF() + if not isinstance(signatures[0], tuple): + sigs = (signatures,) + else: + sigs = signatures + for sig in sigs: + gf.register(sig, meth) return gf return _wrap diff --git a/dispatch.py b/dispatch.py --- a/dispatch.py +++ b/dispatch.py @@ -13,7 +13,7 @@ for d, s, order in zip(dom, sub, orders)) return [cand for cand, _func in candidates if not any(dominates(dom, cand) - for dom, _xxx in candidates)] + for dom, _func in candidates)] def clos_mro(types_func_map, callsig): siglen = len(callsig) @@ -27,32 +27,48 @@ return (x[1] for x in sorted((map(proximity, sig, mros), func) for sig, func in table)) + +class NoNextMethod(Exception): pass + class GF(object): def __init__(self, mro=clos_mro): self.mro = mro self._reg = {} self._cache = {} + self._funcs = list() + self._nm_index = 0 + + def __str__(self): + return '' % ( + self._funcs, self.mro, self._reg, self._cache, self._nm_index) def register(self, sig, func): self._reg[sig] = func self._cache = {} def __call__(self, *args, **kwargs): - self._pos = 0 + self._nm_index = 0 sig = tuple(x.__class__ for x in args) func = self._cache.get(sig) if func is None: self._funcs = list(self.mro(self._reg, sig)) try: - self._cache[sig] = func = self._funcs[self._pos] + self._cache[sig] = func = self._funcs[self._nm_index] except IndexError: raise TypeError('no defined call signature for args (%s)' % ','.join(str(arg) for arg in args)) return func(*args, **kwargs) - def next_method(self, *args, **kwargs): - self._pos += 1 - func = self._funcs[self._pos] - return func(*args, **kwargs) + def next_method(self): + self._nm_index += 1 + try: + func = self._funcs[self._nm_index] + except IndexError: + return None + return func - + def call_next_method(self, *args, **kwargs): + next = self.next_method() + if next: + return next(*args, **kwargs) + raise NoNextMethod(str(self)) diff --git a/test/test_gf.py b/test/test_gf.py --- a/test/test_gf.py +++ b/test/test_gf.py @@ -1,6 +1,6 @@ import unittest from gf import method -from gf.dispatch import GF +from gf.dispatch import GF, NoNextMethod class TestGF(unittest.TestCase): @@ -27,15 +27,15 @@ l.append('top') @method(Left, list) def bip(x, l): - bip.next_method(x, l) + bip.call_next_method(x, l) l.append('left') @method(Right, list) def bip(x, l): - bip.next_method(x, l) + bip.call_next_method(x, l) l.append('right') @method(Bottom, list) def bip(x, l): - bip.next_method(x, l) + bip.call_next_method(x, l) l.append('bottom') b = Bottom() @@ -56,13 +56,13 @@ return ('AX',) @method(A,Y) def foo(x,y): - return foo.next_method(x, y) + ('AY',) + return foo.call_next_method(x, y) + ('AY',) @method(B,X) def foo(x,y): - return foo.next_method(x, y) + ('BX',) + return foo.call_next_method(x, y) + ('BX',) @method(B,Y) def foo(x,y): - return foo.next_method(x, y) + ('BY',) + return foo.call_next_method(x, y) + ('BY',) b, y = B(), Y() self.assertEquals(foo(b, y), ('AX', 'AY', 'BX', 'BY')) @@ -78,8 +78,37 @@ @method(B,X) def foo(b,x): return b,x - b, y = B(), Y() + a, b, x, y = A(), B(), X(), Y() self.assertRaises(TypeError, foo, b, y) + self.assertEquals(foo(a, y), (a, y)) + self.assertEquals(foo(b, x), (b, x)) + self.assertRaises(TypeError, foo, a, x) + + def test_call_next_method(self): + class A(object): pass + class B(object): pass + @method((A,), (B,)) + def check(obj): + check.call_next_method(obj) + self.assertRaises(NoNextMethod, check, B()) + + def test_c3_mro(self): + class pane(object): pass + class scrolling_mixin(object): pass + class editing_mixin(object): pass + class scrollable_pane(pane, scrolling_mixin): pass + class editable_pane(pane, editing_mixin): pass + class editable_scrollable_pane(scrollable_pane, + editable_pane): pass + order = (editable_scrollable_pane, + scrollable_pane, + editable_pane, + pane, + scrolling_mixin, + editing_mixin, + object) + self.assertEquals(tuple(editable_scrollable_pane.mro()), + order) if __name__ == '__main__': unittest.main()