From 1477cfefb11ac30d44f388135d8e036adcf17ef3 Mon Sep 17 00:00:00 2001 From: Marius Van Niekerk Date: Wed, 27 Dec 2017 14:07:55 -0500 Subject: [PATCH 01/12] Experiment to use pytypes to add support for python type hints Most of the actual hard bits are delegated to pytypes for determining if types match expected instances. --- .travis.yml | 1 + multipledispatch/conflict.py | 6 +++-- multipledispatch/dispatcher.py | 16 +++++++++----- .../tests/test_dispatcher_3only.py | 16 ++++++++++++++ multipledispatch/utils.py | 22 ++++++++++++++----- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0814116..ccbdc43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,7 @@ python: install: - pip install coverage - pip install --upgrade pytest pytest-benchmark + - pip install pytypes script: - | diff --git a/multipledispatch/conflict.py b/multipledispatch/conflict.py index 8120a0d..5dc2491 100644 --- a/multipledispatch/conflict.py +++ b/multipledispatch/conflict.py @@ -1,4 +1,6 @@ from .utils import _toposort, groupby +from pytypes import is_subtype + class AmbiguityWarning(Warning): pass @@ -6,13 +8,13 @@ class AmbiguityWarning(Warning): def supercedes(a, b): """ A is consistent and strictly more specific than B """ - return len(a) == len(b) and all(map(issubclass, a, b)) + return len(a) == len(b) and all(map(is_subtype, a, b)) def consistent(a, b): """ It is possible for an argument list to satisfy both A and B """ return (len(a) == len(b) and - all(issubclass(aa, bb) or issubclass(bb, aa) + all(is_subtype(aa, bb) or is_subtype(bb, aa) for aa, bb in zip(a, b))) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index c2cd82c..235761f 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -2,8 +2,10 @@ import inspect from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning from .utils import expand_tuples -import itertools as itl +import itertools as itl +import pytypes +import typing class MDNotImplementedError(NotImplementedError): """ A NotImplementedError for multiple dispatch """ @@ -111,7 +113,7 @@ def get_func_params(cls, func): @classmethod def get_func_annotations(cls, func): - """ get annotations of function positional paremeters + """ get annotations of function positional parameters """ params = cls.get_func_params(func) if params: @@ -135,6 +137,7 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): >>> D = Dispatcher('add') >>> D.add((int, int), lambda x, y: x + y) >>> D.add((float, float), lambda x, y: x + y) + >>> D.add((typing.Optional[str], ), lambda x: x) >>> D(1, 2) 3 @@ -142,6 +145,9 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): Traceback (most recent call last): ... NotImplementedError: Could not find signature for add: + >>> D('s') + 's' + >>> D(None) When ``add`` detects a warning it calls the ``on_ambiguity`` callback with a dispatcher/itself, and a set of ambiguous type signature pairs @@ -154,7 +160,7 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): signature = annotations # Handle union types - if any(isinstance(typ, tuple) for typ in signature): + if any(isinstance(typ, tuple) or pytypes.is_Union(typ) for typ in signature): for typs in expand_tuples(signature): self.add(typs, func, on_ambiguity) return @@ -182,7 +188,7 @@ def reorder(self, on_ambiguity=ambiguity_warn): _unresolved_dispatchers.add(self) def __call__(self, *args, **kwargs): - types = tuple([type(arg) for arg in args]) + types = tuple([pytypes.deep_type(arg) for arg in args]) try: func = self._cache[types] except KeyError: @@ -244,7 +250,7 @@ def dispatch(self, *types): def dispatch_iter(self, *types): n = len(types) for signature in self.ordering: - if len(signature) == n and all(map(issubclass, types, signature)): + if len(signature) == n and all(map(pytypes.is_subtype, types, signature)): result = self.funcs[signature] yield result diff --git a/multipledispatch/tests/test_dispatcher_3only.py b/multipledispatch/tests/test_dispatcher_3only.py index b041450..a38f1d3 100644 --- a/multipledispatch/tests/test_dispatcher_3only.py +++ b/multipledispatch/tests/test_dispatcher_3only.py @@ -4,6 +4,7 @@ from multipledispatch import dispatch from multipledispatch.dispatcher import Dispatcher +import typing def test_function_annotation_register(): @@ -30,8 +31,23 @@ def inc(x: int): def inc(x: float): return x - 1 + @dispatch() + def inc(x: typing.Optional[str]): + return x + + @dispatch() + def inc(x: typing.List[int]): + return x[0] * 4 + + @dispatch() + def inc(x: typing.List[str]): + return x[0] + 'b' + assert inc(1) == 2 assert inc(1.0) == 0.0 + assert inc('a') == 'a' + assert inc([8]) == 32 + assert inc(['a']) == 'ab' def test_function_annotation_dispatch_custom_namespace(): diff --git a/multipledispatch/utils.py b/multipledispatch/utils.py index 4f49a10..e1d2474 100644 --- a/multipledispatch/utils.py +++ b/multipledispatch/utils.py @@ -1,3 +1,8 @@ + +import pytypes +import typing + + def raises(err, lamda): try: lamda() @@ -14,15 +19,22 @@ def expand_tuples(L): >>> expand_tuples([1, 2]) [(1, 2)] + + >>> expand_tuples([1, typing.Optional[str]]) + [(1, ), (1, )] """ if not L: return [()] - elif not isinstance(L[0], tuple): - rest = expand_tuples(L[1:]) - return [(L[0],) + t for t in rest] else: - rest = expand_tuples(L[1:]) - return [(item,) + t for t in rest for item in L[0]] + if pytypes.is_Union(L[0]): + rest = expand_tuples(L[1:]) + return [(item,) + t for t in rest for item in pytypes.get_Union_params(L[0])] + elif not pytypes.is_of_type(L[0], tuple): + rest = expand_tuples(L[1:]) + return [(L[0],) + t for t in rest] + else: + rest = expand_tuples(L[1:]) + return [(item,) + t for t in rest for item in L[0]] # Taken from theano/theano/gof/sched.py From 8b0fa6bfea1c59af4939fffd29e5a0186d28e6e4 Mon Sep 17 00:00:00 2001 From: Marius Van Niekerk Date: Wed, 27 Dec 2017 15:58:28 -0500 Subject: [PATCH 02/12] Change test to pass with pytypes for now --- multipledispatch/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 235761f..4e70a52 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -141,10 +141,10 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): >>> D(1, 2) 3 - >>> D(1, 2.0) + >>> D('1', 2.0) Traceback (most recent call last): ... - NotImplementedError: Could not find signature for add: + NotImplementedError: Could not find signature for add: >>> D('s') 's' >>> D(None) From 184df19bdb539b583c7426eba7368e0af55c2bb4 Mon Sep 17 00:00:00 2001 From: Marius Van Niekerk Date: Wed, 27 Dec 2017 17:16:07 -0500 Subject: [PATCH 03/12] Change doctest to work in py2/3 due to difference in reprs --- multipledispatch/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/multipledispatch/utils.py b/multipledispatch/utils.py index e1d2474..816c7a4 100644 --- a/multipledispatch/utils.py +++ b/multipledispatch/utils.py @@ -20,8 +20,8 @@ def expand_tuples(L): >>> expand_tuples([1, 2]) [(1, 2)] - >>> expand_tuples([1, typing.Optional[str]]) - [(1, ), (1, )] + >>> expand_tuples([1, typing.Optional[str]]) #doctest: +ELLIPSIS + [(1, <... 'str'>), (1, <... 'NoneType'>)] """ if not L: return [()] From bbbb2924c898109157e43f2468805fb2e665baab Mon Sep 17 00:00:00 2001 From: Marius Van Niekerk Date: Wed, 27 Dec 2017 19:56:16 -0500 Subject: [PATCH 04/12] Remove python versions that dont support From 5a8e40113d76ee0012244bbecbf26b16391997b3 Mon Sep 17 00:00:00 2001 From: Marius Van Niekerk Date: Thu, 28 Dec 2017 11:47:39 -0500 Subject: [PATCH 05/12] Work towards supporting diagonal dispatch --- multipledispatch/conflict.py | 15 ++++- multipledispatch/dispatcher.py | 57 +++++++++++++++---- .../tests/test_dispatcher_3only.py | 13 +++++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/multipledispatch/conflict.py b/multipledispatch/conflict.py index 5dc2491..abe502c 100644 --- a/multipledispatch/conflict.py +++ b/multipledispatch/conflict.py @@ -1,20 +1,29 @@ from .utils import _toposort, groupby -from pytypes import is_subtype +from pytypes import is_subtype, is_Union, get_Union_params +from itertools import zip_longest class AmbiguityWarning(Warning): pass +def safe_subtype(a, b): + """Union safe subclass""" + if is_Union(a): + return any(is_subtype(tp, b) for tp in get_Union_params(a)) + else: + return is_subtype(a, b) + + def supercedes(a, b): """ A is consistent and strictly more specific than B """ - return len(a) == len(b) and all(map(is_subtype, a, b)) + return len(a) == len(b) and all(map(safe_subtype, a, b)) def consistent(a, b): """ It is possible for an argument list to satisfy both A and B """ return (len(a) == len(b) and - all(is_subtype(aa, bb) or is_subtype(bb, aa) + all(safe_subtype(aa, bb) or safe_subtype(bb, aa) for aa, bb in zip(a, b))) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 4e70a52..68d28d3 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -1,5 +1,8 @@ from warnings import warn import inspect + +import copy + from .conflict import ordering, ambiguities, super_signature, AmbiguityWarning from .utils import expand_tuples @@ -158,15 +161,43 @@ def add(self, signature, func, on_ambiguity=ambiguity_warn): annotations = self.get_func_annotations(func) if annotations: signature = annotations + # Make function annotation dict + argspec = pytypes.getargspecs(func) + if pytypes.is_classmethod(func) or pytypes.is_method(func): + arg_names = argspec.args[1:] + else: + arg_names = argspec.args + + def process_union(tp): + if isinstance(tp, tuple): + t = typing.Union[tp] + return t + else: + return tp + signature = tuple(process_union(tp) for tp in signature) + annotations = dict(zip(arg_names, signature)) + # make a copy of the function (if needed) and apply the function annotations + if (not hasattr(func, '__annotations__')) or (not func.__annotations__): + func.__annotations__ = annotations + else: + if func.__annotations__ != annotations: + func = copy.deepcopy(func) + func.__annotations__ = annotations + + + # TODO: REMOVE THIS # Handle union types - if any(isinstance(typ, tuple) or pytypes.is_Union(typ) for typ in signature): - for typs in expand_tuples(signature): - self.add(typs, func, on_ambiguity) - return + #if any(isinstance(typ, tuple) or pytypes.is_Union(typ) for typ in signature): + # for typs in expand_tuples(signature): + # self.add(typs, func, on_ambiguity) + # return + # TODO: MAKE THIS type or typevar for typ in signature: - if not isinstance(typ, type): + try: + typing.Union[typ] + except TypeError: str_sig = ', '.join(c.__name__ if isinstance(c, type) else str(c) for c in signature) raise TypeError("Tried to dispatch on non-type: %s\n" @@ -192,7 +223,7 @@ def __call__(self, *args, **kwargs): try: func = self._cache[types] except KeyError: - func = self.dispatch(*types) + func = self.dispatch(*types, args=args) if not func: raise NotImplementedError( 'Could not find signature for %s: <%s>' % @@ -217,7 +248,7 @@ def __str__(self): return "" % self.name __repr__ = __str__ - def dispatch(self, *types): + def dispatch(self, *types, args=None): """Deterimine appropriate implementation for this type signature This method is internal. Users should call this object as a function. @@ -243,16 +274,20 @@ def dispatch(self, *types): return self.funcs[types] try: - return next(self.dispatch_iter(*types)) + return next(self.dispatch_iter(*types, args=args)) except StopIteration: return None - def dispatch_iter(self, *types): + def dispatch_iter(self, *types, args=None): n = len(types) for signature in self.ordering: - if len(signature) == n and all(map(pytypes.is_subtype, types, signature)): + if len(signature) == n: result = self.funcs[signature] - yield result + try: + if pytypes.check_argument_types(result, call_args=args): + yield result + except pytypes.InputTypeError: + continue def resolve(self, types): """ Deterimine appropriate implementation for this type signature diff --git a/multipledispatch/tests/test_dispatcher_3only.py b/multipledispatch/tests/test_dispatcher_3only.py index a38f1d3..c57259d 100644 --- a/multipledispatch/tests/test_dispatcher_3only.py +++ b/multipledispatch/tests/test_dispatcher_3only.py @@ -4,6 +4,7 @@ from multipledispatch import dispatch from multipledispatch.dispatcher import Dispatcher +from multipledispatch.utils import raises import typing @@ -84,6 +85,18 @@ def f(self, x: float): assert foo.f(1.0) == 0.0 +def test_diagonal_dispatch(): + T = typing.TypeVar('T') + U = typing.TypeVar('U') + + @dispatch() + def diag(x: T, y: T): + return 'same' + + assert diag(1, 6) == 'same' + assert raises(NotImplementedError, lambda: diag(1, '1')) + + def test_overlaps(): @dispatch(int) def inc(x: int): From 0cd626519f902d1e91926cf66ec61477b2b907d1 Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Mon, 26 Feb 2018 18:51:23 -0500 Subject: [PATCH 06/12] Cleanup behavior for diagonal dispatch --- multipledispatch/dispatcher.py | 29 ++++++++++++++++++++--------- multipledispatch/tests/test_core.py | 6 +++--- multipledispatch/utils.py | 3 +++ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index b2e4781..ff52926 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -51,6 +51,7 @@ def restart_ordering(on_ambiguity=ambiguity_warn): DeprecationWarning, ) + class Dispatcher(object): """ Dispatch methods based on type signature @@ -73,11 +74,12 @@ class Dispatcher(object): >>> f(3.0) 2.0 """ - __slots__ = '__name__', 'name', 'funcs', '_ordering', '_cache', 'doc' + __slots__ = '__name__', 'name', 'funcs', 'annotations', '_ordering', '_cache', 'doc' def __init__(self, name, doc=None): self.name = self.__name__ = name self.funcs = {} + self.annotations = {} self.doc = doc self._cache = {} @@ -183,12 +185,13 @@ def process_union(tp): annotations = dict(zip(arg_names, signature)) # make a copy of the function (if needed) and apply the function annotations - if (not hasattr(func, '__annotations__')) or (not func.__annotations__): - func.__annotations__ = annotations - else: - if func.__annotations__ != annotations: - func = copy.deepcopy(func) - func.__annotations__ = annotations + # if (not hasattr(func, '__annotations__')) or (not func.__annotations__): + # func.__annotations__ = annotations + # else: + # if func.__annotations__ != annotations: + # import functools + # func = functools.wraps(func) + # func.__annotations__ = annotations # TODO: REMOVE THIS @@ -211,6 +214,7 @@ def process_union(tp): (typ, str_sig, self.name)) self.funcs[signature] = func + self.annotations[signature] = annotations self._cache.clear() try: @@ -301,8 +305,13 @@ def dispatch_iter(self, *types, args=None): for signature in self.ordering: if len(signature) == n: result = self.funcs[signature] + annotations = self.annotations[signature] + result.__annotations__ = annotations try: - if pytypes.check_argument_types(result, call_args=args): + if args is None: + if pytypes.is_subtype(typing.Tuple[types], typing.Tuple[signature]): + yield result + elif pytypes.check_argument_types(result, call_args=args): yield result except pytypes.InputTypeError: continue @@ -320,11 +329,13 @@ def resolve(self, types): def __getstate__(self): return {'name': self.name, - 'funcs': self.funcs} + 'funcs': self.funcs, + 'annotations': self.annotations} def __setstate__(self, d): self.name = d['name'] self.funcs = d['funcs'] + self.annotations = d['annotations'] self._ordering = ordering(self.funcs) self._cache = dict() diff --git a/multipledispatch/tests/test_core.py b/multipledispatch/tests/test_core.py index d3f6eec..763df69 100644 --- a/multipledispatch/tests/test_core.py +++ b/multipledispatch/tests/test_core.py @@ -131,11 +131,11 @@ def f(x): def test_union_types(): @dispatch((A, C)) - def f(x): + def hh(x): return 1 - assert f(A()) == 1 - assert f(C()) == 1 + assert hh(A()) == 1 + assert hh(C()) == 1 def test_namespaces(): diff --git a/multipledispatch/utils.py b/multipledispatch/utils.py index 816c7a4..8701756 100644 --- a/multipledispatch/utils.py +++ b/multipledispatch/utils.py @@ -32,6 +32,9 @@ def expand_tuples(L): elif not pytypes.is_of_type(L[0], tuple): rest = expand_tuples(L[1:]) return [(L[0],) + t for t in rest] + elif not isinstance(L[0], tuple): + rest = expand_tuples(L[1:]) + return [(L[0],) + t for t in rest] else: rest = expand_tuples(L[1:]) return [(item,) + t for t in rest for item in L[0]] From e72819c8db75735d501b0cef3fca828bb613a33a Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Mon, 26 Feb 2018 20:43:18 -0500 Subject: [PATCH 07/12] Hacky: More type bits --- multipledispatch/dispatcher.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index ff52926..fb8fba5 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -169,20 +169,26 @@ def add(self, signature, func): if annotations: signature = annotations # Make function annotation dict - argspec = pytypes.getargspecs(func) - if pytypes.is_classmethod(func) or pytypes.is_method(func): + if hasattr(func, '__call__'): + argspec_func = func.__call__ + else: + argspec_func = func + argspec = pytypes.getargspecs(argspec_func) + if pytypes.is_classmethod(argspec_func) or pytypes.is_method(argspec_func): arg_names = argspec.args[1:] else: arg_names = argspec.args def process_union(tp): if isinstance(tp, tuple): - t = typing.Union[tp] + t = typing.Union[tuple(process_union(e) for e in tp)] return t else: return tp signature = tuple(process_union(tp) for tp in signature) - annotations = dict(zip(arg_names, signature)) + import string + suffix_args = ['__' + c for c in string.ascii_lowercase][:len(signature) - len(arg_names)] + annotations = dict(zip(list(arg_names) + suffix_args, signature)) # make a copy of the function (if needed) and apply the function annotations # if (not hasattr(func, '__annotations__')) or (not func.__annotations__): @@ -237,7 +243,10 @@ def reorder(self, on_ambiguity=ambiguity_warn): return od def __call__(self, *args, **kwargs): - types = tuple([pytypes.deep_type(arg) for arg in args]) + try: + types = tuple([pytypes.deep_type(arg, 1, max_sample=10) for arg in args]) + except: + types = tuple([type(arg) for arg in args]) try: func = self._cache[types] except KeyError: @@ -306,12 +315,13 @@ def dispatch_iter(self, *types, args=None): if len(signature) == n: result = self.funcs[signature] annotations = self.annotations[signature] - result.__annotations__ = annotations + def f(): + pass + f.__annotations__ = annotations try: - if args is None: - if pytypes.is_subtype(typing.Tuple[types], typing.Tuple[signature]): - yield result - elif pytypes.check_argument_types(result, call_args=args): + if pytypes.is_subtype(typing.Tuple[types], typing.Tuple[signature]): + yield result + elif pytypes.check_argument_types(f, call_args=args): yield result except pytypes.InputTypeError: continue From a9e5d5f40972ec6a4167491e30ebfe5ef03d325b Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Tue, 27 Feb 2018 09:10:16 -0500 Subject: [PATCH 08/12] Legacy arg support --- multipledispatch/dispatcher.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index fb8fba5..d60594d 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -245,12 +245,14 @@ def reorder(self, on_ambiguity=ambiguity_warn): def __call__(self, *args, **kwargs): try: types = tuple([pytypes.deep_type(arg, 1, max_sample=10) for arg in args]) + dargs = args except: types = tuple([type(arg) for arg in args]) + dargs = None try: func = self._cache[types] except KeyError: - func = self.dispatch(*types, args=args) + func = self.dispatch(*types, args=dargs) if not func: raise NotImplementedError( 'Could not find signature for %s: <%s>' % @@ -260,7 +262,7 @@ def __call__(self, *args, **kwargs): return func(*args, **kwargs) except MDNotImplementedError: - funcs = self.dispatch_iter(*types) + funcs = self.dispatch_iter(*types, args=dargs) next(funcs) # burn first for func in funcs: try: From 5a4ea583228255742b1953c904711a745d4d41ef Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Tue, 27 Feb 2018 09:57:27 -0500 Subject: [PATCH 09/12] Cleanup --- multipledispatch/dispatcher.py | 62 ++++++++++++++-------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index d60594d..696a07f 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -185,43 +185,31 @@ def process_union(tp): return t else: return tp - signature = tuple(process_union(tp) for tp in signature) - import string - suffix_args = ['__' + c for c in string.ascii_lowercase][:len(signature) - len(arg_names)] - annotations = dict(zip(list(arg_names) + suffix_args, signature)) - - # make a copy of the function (if needed) and apply the function annotations - # if (not hasattr(func, '__annotations__')) or (not func.__annotations__): - # func.__annotations__ = annotations - # else: - # if func.__annotations__ != annotations: - # import functools - # func = functools.wraps(func) - # func.__annotations__ = annotations - - - # TODO: REMOVE THIS - # Handle union types - #if any(isinstance(typ, tuple) or pytypes.is_Union(typ) for typ in signature): - # for typs in expand_tuples(signature): - # self.add(typs, func, on_ambiguity) - # return - - # TODO: MAKE THIS type or typevar - for typ in signature: - try: - typing.Union[typ] - except TypeError: - str_sig = ', '.join(c.__name__ if isinstance(c, type) - else str(c) for c in signature) - raise TypeError("Tried to dispatch on non-type: %s\n" - "In signature: <%s>\n" - "In function: %s" % - (typ, str_sig, self.name)) - - self.funcs[signature] = func - self.annotations[signature] = annotations - self._cache.clear() + + signatures = expand_tuples(signature) + for signature in signatures: + signature = tuple(process_union(tp) for tp in signature) + import string + suffix_args = ['__' + c for c in string.ascii_lowercase][:len(signature) - len(arg_names)] + annotations = dict(zip(list(arg_names) + suffix_args, signature)) + + # make a copy of the function (if needed) and apply the function annotations + + # TODO: MAKE THIS type or typevar + for typ in signature: + try: + typing.Union[typ] + except TypeError: + str_sig = ', '.join(c.__name__ if isinstance(c, type) + else str(c) for c in signature) + raise TypeError("Tried to dispatch on non-type: %s\n" + "In signature: <%s>\n" + "In function: %s" % + (typ, str_sig, self.name)) + + self.funcs[signature] = func + self.annotations[signature] = annotations + self._cache.clear() try: del self._ordering From 2ce1c9e291a698d72c4f2ff07f630963621ec8b5 Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Tue, 27 Feb 2018 11:01:28 -0500 Subject: [PATCH 10/12] Simplify need for making silly fake functions for annotations --- multipledispatch/dispatcher.py | 50 ++++++++++++---------------------- 1 file changed, 18 insertions(+), 32 deletions(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index 696a07f..d62d732 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -74,12 +74,11 @@ class Dispatcher(object): >>> f(3.0) 2.0 """ - __slots__ = '__name__', 'name', 'funcs', 'annotations', '_ordering', '_cache', 'doc' + __slots__ = '__name__', 'name', 'funcs', '_ordering', '_cache', 'doc' def __init__(self, name, doc=None): self.name = self.__name__ = name self.funcs = {} - self.annotations = {} self.doc = doc self._cache = {} @@ -169,15 +168,6 @@ def add(self, signature, func): if annotations: signature = annotations # Make function annotation dict - if hasattr(func, '__call__'): - argspec_func = func.__call__ - else: - argspec_func = func - argspec = pytypes.getargspecs(argspec_func) - if pytypes.is_classmethod(argspec_func) or pytypes.is_method(argspec_func): - arg_names = argspec.args[1:] - else: - arg_names = argspec.args def process_union(tp): if isinstance(tp, tuple): @@ -189,9 +179,6 @@ def process_union(tp): signatures = expand_tuples(signature) for signature in signatures: signature = tuple(process_union(tp) for tp in signature) - import string - suffix_args = ['__' + c for c in string.ascii_lowercase][:len(signature) - len(arg_names)] - annotations = dict(zip(list(arg_names) + suffix_args, signature)) # make a copy of the function (if needed) and apply the function annotations @@ -208,7 +195,6 @@ def process_union(tp): (typ, str_sig, self.name)) self.funcs[signature] = func - self.annotations[signature] = annotations self._cache.clear() try: @@ -233,14 +219,13 @@ def reorder(self, on_ambiguity=ambiguity_warn): def __call__(self, *args, **kwargs): try: types = tuple([pytypes.deep_type(arg, 1, max_sample=10) for arg in args]) - dargs = args except: + # some things dont deeptype welkl types = tuple([type(arg) for arg in args]) - dargs = None try: func = self._cache[types] except KeyError: - func = self.dispatch(*types, args=dargs) + func = self.dispatch(*types) if not func: raise NotImplementedError( 'Could not find signature for %s: <%s>' % @@ -250,7 +235,7 @@ def __call__(self, *args, **kwargs): return func(*args, **kwargs) except MDNotImplementedError: - funcs = self.dispatch_iter(*types, args=dargs) + funcs = self.dispatch_iter(*types) next(funcs) # burn first for func in funcs: try: @@ -269,7 +254,7 @@ def __str__(self): return "" % self.name __repr__ = __str__ - def dispatch(self, *types, args=None): + def dispatch(self, *types): """Deterimine appropriate implementation for this type signature This method is internal. Users should call this object as a function. @@ -295,23 +280,26 @@ def dispatch(self, *types, args=None): return self.funcs[types] try: - return next(self.dispatch_iter(*types, args=args)) + return next(self.dispatch_iter(*types)) except StopIteration: return None - def dispatch_iter(self, *types, args=None): + @staticmethod + def get_type_vars(x): + if isinstance(x, typing.TypeVar): + yield x + if isinstance(x, typing.GenericMeta): + yield from x.__parameters__ + + def dispatch_iter(self, *types): n = len(types) for signature in self.ordering: if len(signature) == n: result = self.funcs[signature] - annotations = self.annotations[signature] - def f(): - pass - f.__annotations__ = annotations try: - if pytypes.is_subtype(typing.Tuple[types], typing.Tuple[signature]): - yield result - elif pytypes.check_argument_types(f, call_args=args): + typsig = typing.Tuple[signature] + typvars = list(self.get_type_vars(typsig)) + if pytypes.is_subtype(typing.Tuple[types], typsig, bound_typevars={t.__name__: t for t in typvars}): yield result except pytypes.InputTypeError: continue @@ -329,13 +317,11 @@ def resolve(self, types): def __getstate__(self): return {'name': self.name, - 'funcs': self.funcs, - 'annotations': self.annotations} + 'funcs': self.funcs} def __setstate__(self, d): self.name = d['name'] self.funcs = d['funcs'] - self.annotations = d['annotations'] self._ordering = ordering(self.funcs) self._cache = dict() From cbbf0be221a41c01ecbcd09930e82e53078fdf0b Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Tue, 27 Feb 2018 11:18:25 -0500 Subject: [PATCH 11/12] py27 --- multipledispatch/dispatcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/multipledispatch/dispatcher.py b/multipledispatch/dispatcher.py index d62d732..f8dc6ab 100644 --- a/multipledispatch/dispatcher.py +++ b/multipledispatch/dispatcher.py @@ -289,7 +289,8 @@ def get_type_vars(x): if isinstance(x, typing.TypeVar): yield x if isinstance(x, typing.GenericMeta): - yield from x.__parameters__ + for e in x.__parameters__: + yield e def dispatch_iter(self, *types): n = len(types) From a558c0f11a0025356675d5176fbd169832b26d36 Mon Sep 17 00:00:00 2001 From: Marius van Niekerk Date: Tue, 27 Feb 2018 11:25:37 -0500 Subject: [PATCH 12/12] py27 --- multipledispatch/conflict.py | 1 - 1 file changed, 1 deletion(-) diff --git a/multipledispatch/conflict.py b/multipledispatch/conflict.py index abe502c..0416391 100644 --- a/multipledispatch/conflict.py +++ b/multipledispatch/conflict.py @@ -1,6 +1,5 @@ from .utils import _toposort, groupby from pytypes import is_subtype, is_Union, get_Union_params -from itertools import zip_longest class AmbiguityWarning(Warning):