From 0fd50a2ca94a64a15da5012740d2d5c49ead54fa Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Fri, 28 Feb 2014 10:23:04 -0500 Subject: [PATCH 1/5] gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0d20b64..d71c50a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ *.pyc +*.egg-info +*.idea \ No newline at end of file From 1b4c319c155f731f5fb1116bf7726824460bcae7 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Fri, 28 Feb 2014 12:09:42 -0500 Subject: [PATCH 2/5] check for method instances at runtime --- multipledispatch/core.py | 55 +++++++++++++++------------------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/multipledispatch/core.py b/multipledispatch/core.py index df2719d..706e228 100644 --- a/multipledispatch/core.py +++ b/multipledispatch/core.py @@ -25,12 +25,13 @@ class Dispatcher(object): >>> f(3.0) 2.0 """ - __slots__ = 'name', 'funcs', 'ordering', '_cache' + __slots__ = 'name', 'funcs', 'ordering', '_cache', 'instance' def __init__(self, name): self.name = name self.funcs = dict() self._cache = dict() + self.instance = None def add(self, signature, func): """ Add new types/method pair to dispatcher @@ -56,7 +57,15 @@ def add(self, signature, func): def __call__(self, *args, **kwargs): types = tuple([type(arg) for arg in args]) func = self.resolve(types) - return func(*args, **kwargs) + # check if the Dispatcher has an instance attribute. + # If so, it is being called as a bound method + if self.instance is not None: + result = func(self.instance, *args, **kwargs) + # set instance to None to reset the Dispatcher + self.instance = None + return result + else: + return func(*args, **kwargs) def __str__(self): return "" % self.name @@ -100,23 +109,14 @@ def resolve(self, types): return result raise NotImplementedError() - -class MethodDispatcher(Dispatcher): - """ Dispatch methods based on type signature - - See Also: - Dispatcher - """ - def __get__(self, instance, owner): - self.obj = instance - self.cls = owner + def __get__(self, instance, typ): + """ + This is only called if the Dispatcher is decorating a method. In that + case, the instance attribute is set. + """ + self.instance = instance return self - def __call__(self, *args, **kwargs): - types = tuple([type(arg) for arg in args]) - func = self.resolve(types) - return func(self.obj, *args, **kwargs) - global_namespace = dict() @@ -169,13 +169,9 @@ def dispatch(*types, **kwargs): def _(func): name = func.__name__ - if ismethod(func): - dispatcher = inspect.currentframe().f_back.f_locals.get(name, - MethodDispatcher(name)) - else: - if name not in namespace: - namespace[name] = Dispatcher(name) - dispatcher = namespace[name] + if name not in namespace: + namespace[name] = Dispatcher(name) + dispatcher = namespace[name] for typs in expand_tuples(types): dispatcher.add(typs, func) @@ -183,15 +179,6 @@ def _(func): return _ -def ismethod(func): - """ Is func a method? - - Note that this has to work as the method is defined but before the class is - defined. At this stage methods look like functions. - """ - spec = inspect.getargspec(func) - return spec and spec.args and spec.args[0] == 'self' - def expand_tuples(L): """ @@ -230,4 +217,4 @@ def warning_text(name, amb): text += "\n\nConsider making the following additions:\n\n" text += '\n\n'.join(['@dispatch(' + str_signature(super_signature(s)) + ')\ndef %s(...)'%name for s in amb]) - return text + return text \ No newline at end of file From 8990a68eec5ecdd9d99a5616416a11e4632a5fbf Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Fri, 28 Feb 2014 12:09:53 -0500 Subject: [PATCH 3/5] avoid name collisions with methods because test order is random --- multipledispatch/tests/test_core.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/multipledispatch/tests/test_core.py b/multipledispatch/tests/test_core.py index dcc2cc9..d293092 100644 --- a/multipledispatch/tests/test_core.py +++ b/multipledispatch/tests/test_core.py @@ -171,36 +171,36 @@ def q(x): def test_methods(): class Foo(object): @dispatch(float) - def f(self, x): + def methodf(self, x): return x - 1 @dispatch(int) - def f(self, x): + def methodf(self, x): return x + 1 @dispatch(int) - def g(self, x): + def methodg(self, x): return x + 3 foo = Foo() - assert foo.f(1) == 2 - assert foo.f(1.0) == 0.0 - assert foo.g(1) == 4 + assert foo.methodf(1) == 2 + assert foo.methodf(1.0) == 0.0 + assert foo.methodg(1) == 4 def test_methods_multiple_dispatch(): class Foo(object): @dispatch(A, A) - def f(x, y): + def methodf(self, x, y): return 1 @dispatch(A, C) - def f(x, y): + def methodf(self, x, y): return 2 foo = Foo() - assert foo.f(A(), A()) == 1 - assert foo.f(A(), C()) == 2 - assert foo.f(C(), C()) == 2 + assert foo.methodf(A(), A()) == 1 + assert foo.methodf(A(), C()) == 2 + assert foo.methodf(C(), C()) == 2 From db993040a68a2562027be4ba8bac1a909d8449d2 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Sat, 1 Mar 2014 08:24:13 -0500 Subject: [PATCH 4/5] remove global namespace and restore frame inspection --- multipledispatch/core.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/multipledispatch/core.py b/multipledispatch/core.py index 706e228..e5728c9 100644 --- a/multipledispatch/core.py +++ b/multipledispatch/core.py @@ -118,9 +118,6 @@ def __get__(self, instance, typ): return self -global_namespace = dict() - - def dispatch(*types, **kwargs): """ Dispatch function on the types of the inputs @@ -164,22 +161,25 @@ def dispatch(*types, **kwargs): ... def __init__(self, datum): ... self.data = [datum] """ - namespace = kwargs.get('namespace', global_namespace) + namespace = kwargs.get('namespace', None) types = tuple(types) + def _(func): name = func.__name__ - - if name not in namespace: - namespace[name] = Dispatcher(name) - dispatcher = namespace[name] - + frame = inspect.currentframe() + if namespace is not None: + if name not in namespace: + namespace[name] = frame.f_locals.get( + name, Dispatcher(name)) + dispatcher = namespace[name] + else: + dispatcher = frame.f_back.f_locals.get(name, Dispatcher(name)) + del frame for typs in expand_tuples(types): dispatcher.add(typs, func) return dispatcher return _ - - def expand_tuples(L): """ From 8da0cfcafc6f3ccb3342b25eb900c7f12d5c581a Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin Date: Sat, 1 Mar 2014 08:24:26 -0500 Subject: [PATCH 5/5] new test for method/function collisions --- multipledispatch/tests/test_core.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/multipledispatch/tests/test_core.py b/multipledispatch/tests/test_core.py index d293092..7ac8dc7 100644 --- a/multipledispatch/tests/test_core.py +++ b/multipledispatch/tests/test_core.py @@ -204,3 +204,36 @@ def methodf(self, x, y): assert foo.methodf(A(), A()) == 1 assert foo.methodf(A(), C()) == 2 assert foo.methodf(C(), C()) == 2 + + +def test_methods_functions_collision(): + + class Foo(object): + @orig_dispatch(int) + def f(self, x): + return x + 1 + + @orig_dispatch(float) + def f(self, x): + return x - 1 + + @orig_dispatch(int) + def f(x): + return x + 10 + + foo = Foo() + assert foo.f(1) == 2 + assert foo.f(1.0) == 0 + assert f(1) == 11 + + class Foo(object): + @dispatch(int) + def f(self, x): + return x + 1 + + @dispatch(int) + def f(x): + return x + 10 + + foo = Foo() + assert raises(TypeError, lambda: foo.f(1)) \ No newline at end of file