def test_docstring(): def one(x, y): """ Docstring number one """ return x + y def two(x, y): """ Docstring number two """ return x + y def three(x, y): return x + y master_doc = 'Doc of the multimethod itself' f = Dispatcher('f', doc=master_doc) f.add((object, object), one) f.add((int, int), two) f.add((float, float), three) assert one.__doc__.strip() in f.__doc__ assert two.__doc__.strip() in f.__doc__ assert f.__doc__.find(one.__doc__.strip()) < \ f.__doc__.find(two.__doc__.strip()) assert 'object, object' in f.__doc__ assert master_doc in f.__doc__
def test_dispatch_method(): f = Dispatcher('f') @f.register(list) def rev(x): return x[::-1] @f.register(int, int) def add(x, y): return x + y class MyList(list): pass assert f.dispatch(list) is rev assert f.dispatch(MyList) is rev assert f.dispatch(int, int) is add
def test_not_implemented_error(): f = Dispatcher('f') @f.register(float) def _(a): raise MDNotImplementedError() assert raises(NotImplementedError, lambda: f(1.0))
def test_dispatcher(): f = Dispatcher('f') f.add((int, ), inc) f.add((float, ), dec) with warns(DeprecationWarning): assert f.resolve((int, )) == inc assert f.dispatch(int) is inc assert f(1) == 2 assert f(1.0) == 0.0
def test_dispatcher(): f = Dispatcher('f') f.add((int,), inc) f.add((float,), dec) with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) assert f.resolve((int,)) == inc assert f.dispatch(int) is inc assert f(1) == 2 assert f(1.0) == 0.0
def __new__(cls, clsname, bases, dct): # If handler is not defined, assign empty dispatcher. if "handler" not in dct: name = f"Ask{clsname.capitalize()}Handler" handler = Dispatcher(name, doc="Handler for key %s" % name) dct["handler"] = handler dct["_orig_doc"] = dct.get("__doc__", "") return super().__new__(cls, clsname, bases, dct)
def test_dispatcher(): f = Dispatcher('f') f.add((int,), inc) f.add((float,), dec) with warns(DeprecationWarning): assert f.resolve((int,)) == inc assert f.dispatch(int) is inc assert f(1) == 2 assert f(1.0) == 0.0
def test_register_stacking(): f = Dispatcher('f') @f.register(list) @f.register(tuple) def rev(x): return x[::-1] assert f((1, 2, 3)) == (3, 2, 1) assert f([1, 2, 3]) == [3, 2, 1] assert raises(NotImplementedError, lambda: f('hello')) assert rev('hello') == 'olleh'
def test_dispatcher_as_decorator(): f = Dispatcher('f') @f.register(int) def inc(x): # noqa:F811 return x + 1 @f.register(float) # noqa:F811 def inc(x): # noqa:F811 return x - 1 assert f(1) == 2 assert f(1.0) == 0.0
def test_help(): def one(x, y): """ Docstring number one """ return x + y def two(x, y): """ Docstring number two """ return x + y def three(x, y): """ Docstring number three """ return x + y master_doc = 'Doc of the multimethod itself' f = Dispatcher('f', doc=master_doc) f.add((object, object), one) f.add((int, int), two) f.add((float, float), three) assert f._help(1, 1) == two.__doc__ assert f._help(1.0, 2.0) == three.__doc__
def test_halt_method_resolution(): g = [0] def on_ambiguity(a, b): g[0] += 1 f = Dispatcher('f') halt_ordering() def func(*args): pass f.add((int, object), func) f.add((object, int), func) assert g == [0] restart_ordering(on_ambiguity=on_ambiguity) assert g == [1] assert set(f.ordering) == {(int, object), (object, int)}
def test_on_ambiguity(): f = Dispatcher('f') def identity(x): return x ambiguities = [False] def on_ambiguity(dispatcher, amb): ambiguities[0] = True f.add((object, object), identity, on_ambiguity=on_ambiguity) assert not ambiguities[0] f.add((object, float), identity, on_ambiguity=on_ambiguity) assert not ambiguities[0] f.add((float, object), identity, on_ambiguity=on_ambiguity) assert ambiguities[0]
def test_halt_method_resolution(): g = [0] def on_ambiguity(a, b): g[0] += 1 f = Dispatcher('f') halt_ordering() def func(*args): pass f.add((int, object), func) f.add((object, int), func) assert g == [0] restart_ordering(on_ambiguity=on_ambiguity) assert g == [1] assert set(f.ordering) == set([(int, object), (object, int)])
def test_source(): def one(x, y): """ Docstring number one """ return x + y def two(x, y): """ Docstring number two """ return x - y master_doc = 'Doc of the multimethod itself' f = Dispatcher('f', doc=master_doc) f.add((int, int), one) f.add((float, float), two) assert 'x + y' in f._source(1, 1) assert 'x - y' in f._source(1.0, 1.0)
def test_not_implemented(): f = Dispatcher("f") @f.register(object) def _(x): return "default" @f.register(int) def _(x): if x % 2 == 0: return "even" else: raise MDNotImplementedError() assert f("hello") == "default" # default behavior assert f(2) == "even" # specialized behavior assert f(3) == "default" # fall bac to default behavior assert raises(NotImplementedError, lambda: f(1, 2))
def test_not_implemented(): f = Dispatcher('f') @f.register(object) def _(x): return 'default' @f.register(int) def _(x): if x % 2 == 0: return 'even' else: raise MDNotImplementedError() assert f('hello') == 'default' # default behavior assert f(2) == 'even' # specialized behavior assert f(3) == 'default' # fall bac to default behavior assert raises(NotImplementedError, lambda: f(1, 2))
def test_ambiguity_register_error_ignore_dup(): f = Dispatcher('f') class A: pass class B(A): pass class C(A): pass # suppress warning for registering ambiguous signal f.add((A, B), lambda x,y: None, ambiguity_register_error_ignore_dup) f.add((B, A), lambda x,y: None, ambiguity_register_error_ignore_dup) f.add((A, C), lambda x,y: None, ambiguity_register_error_ignore_dup) f.add((C, A), lambda x,y: None, ambiguity_register_error_ignore_dup) # raises error if ambiguous signal is passed assert raises(NotImplementedError, lambda: f(B(), C()))
def __init__(self, name, doc=None): self.name = name self.doc = doc self.handlerattr = "_%s_handler" % name self._handlergetter = attrgetter(self.handlerattr) self._dispatcher = Dispatcher(name)
class KindDispatcher: """ Dispatcher to select a kind from multiple kinds by binary dispatching. .. notes:: This approach is experimental, and can be replaced or deleted in the future. Explanation =========== SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the algebraic structure where the object belongs to. Therefore, with given operation, we can always find a dominating kind among the different kinds. This class selects the kind by recursive binary dispatching. If the result cannot be determined, ``UndefinedKind`` is returned. Examples ======== Multiplication between numbers return number. >>> from sympy import NumberKind, Mul >>> Mul._kind_dispatcher(NumberKind, NumberKind) NumberKind Multiplication between number and unknown-kind object returns unknown kind. >>> from sympy import UndefinedKind >>> Mul._kind_dispatcher(NumberKind, UndefinedKind) UndefinedKind Any number and order of kinds is allowed. >>> Mul._kind_dispatcher(UndefinedKind, NumberKind) UndefinedKind >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind) UndefinedKind Since matrix forms a vector space over scalar field, multiplication between matrix with numeric element and number returns matrix with numeric element. >>> from sympy.matrices import MatrixKind >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind) MatrixKind(NumberKind) If a matrix with number element and another matrix with unknown-kind element are multiplied, we know that the result is matrix but the kind of its elements is unknown. >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind)) MatrixKind(UndefinedKind) Parameters ========== name : str commutative : bool, optional If True, binary dispatch will be automatically registered in reversed order as well. doc : str, optional """ def __init__(self, name, commutative=False, doc=None): self.name = name self.doc = doc self.commutative = commutative self._dispatcher = Dispatcher(name) def __repr__(self): return "<dispatched %s>" % self.name def register(self, *types, **kwargs): """ Register the binary dispatcher for two kind classes. If *self.commutative* is ``True``, signature in reversed order is automatically registered as well. """ on_ambiguity = kwargs.pop("on_ambiguity", None) if not on_ambiguity: if self.commutative: on_ambiguity = ambiguity_register_error_ignore_dup else: on_ambiguity = ambiguity_warn kwargs.update(on_ambiguity=on_ambiguity) if not len(types) == 2: raise RuntimeError( "Only binary dispatch is supported, but got %s types: <%s>." % ( len(types), str_signature(types) )) def _(func): self._dispatcher.add(types, func, **kwargs) if self.commutative: self._dispatcher.add(tuple(reversed(types)), func, **kwargs) return _ def __call__(self, *args, **kwargs): if self.commutative: kinds = frozenset(args) else: kinds = [] prev = None for a in args: if prev is not a: kinds.append(a) prev = a return self.dispatch_kinds(kinds, **kwargs) @cacheit def dispatch_kinds(self, kinds, **kwargs): # Quick exit for the case where all kinds are same if len(kinds) == 1: result, = kinds if not isinstance(result, Kind): raise RuntimeError("%s is not a kind." % result) return result for i,kind in enumerate(kinds): if not isinstance(kind, Kind): raise RuntimeError("%s is not a kind." % kind) if i == 0: result = kind else: prev_kind = result t1, t2 = type(prev_kind), type(kind) k1, k2 = prev_kind, kind func = self._dispatcher.dispatch(t1, t2) if func is None and self.commutative: # try reversed order func = self._dispatcher.dispatch(t2, t1) k1, k2 = k2, k1 if func is None: # unregistered kind relation result = UndefinedKind else: result = func(k1, k2) if not isinstance(result, Kind): raise RuntimeError( "Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format( prev_kind, kind, result )) return result @property def __doc__(self): docs = [ "Kind dispatcher : %s" % self.name, "Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details" ] if self.doc: docs.append(self.doc) s = "Registered kind classes\n" s += '=' * len(s) docs.append(s) amb_sigs = [] typ_sigs = defaultdict(list) for sigs in self._dispatcher.ordering[::-1]: key = self._dispatcher.funcs[sigs] typ_sigs[key].append(sigs) for func, sigs in typ_sigs.items(): sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) if isinstance(func, RaiseNotImplementedError): amb_sigs.append(sigs_str) continue s = 'Inputs: %s\n' % sigs_str s += '-' * len(s) + '\n' if func.__doc__: s += func.__doc__.strip() else: s += func.__name__ docs.append(s) if amb_sigs: s = "Ambiguous kind classes\n" s += '=' * len(s) docs.append(s) s = '\n'.join(amb_sigs) docs.append(s) return '\n\n'.join(docs)
def test_source_raises_on_missing_function(): f = Dispatcher('f') assert raises(TypeError, lambda: f.source(1))
class AssocOpDispatcher: """ Handler dispatcher for associative operators .. notes:: This approach is experimental, and can be replaced or deleted in the future. See https://github.com/sympy/sympy/pull/19463. Explanation =========== If arguments of different types are passed, the classes which handle the operation for each type are collected. Then, a class which performs the operation is selected by recursive binary dispatching. Dispatching relation can be registered by ``register_handlerclass`` method. Priority registration is unordered. You cannot make ``A*B`` and ``B*A`` refer to different handler classes. All logic dealing with the order of arguments must be implemented in the handler class. Examples ======== >>> from sympy import Add, Expr, Symbol >>> from sympy.core.add import add >>> class NewExpr(Expr): ... @property ... def _add_handler(self): ... return NewAdd >>> class NewAdd(NewExpr, Add): ... pass >>> add.register_handlerclass((Add, NewAdd), NewAdd) >>> a, b = Symbol('a'), NewExpr() >>> add(a, b) == NewAdd(a, b) True """ def __init__(self, name, doc=None): self.name = name self.doc = doc self.handlerattr = "_%s_handler" % name self._handlergetter = attrgetter(self.handlerattr) self._dispatcher = Dispatcher(name) def __repr__(self): return "<dispatched %s>" % self.name def register_handlerclass( self, classes, typ, on_ambiguity=ambiguity_register_error_ignore_dup): """ Register the handler class for two classes, in both straight and reversed order. Paramteters =========== classes : tuple of two types Classes who are compared with each other. typ: Class which is registered to represent *cls1* and *cls2*. Handler method of *self* must be implemented in this class. """ if not len(classes) == 2: raise RuntimeError( "Only binary dispatch is supported, but got %s types: <%s>." % (len(classes), str_signature(classes))) if len(set(classes)) == 1: raise RuntimeError("Duplicate types <%s> cannot be dispatched." % str_signature(classes)) self._dispatcher.add(tuple(classes), typ, on_ambiguity=on_ambiguity) self._dispatcher.add(tuple(reversed(classes)), typ, on_ambiguity=on_ambiguity) @cacheit def __call__(self, *args, _sympify=True, **kwargs): """ Parameters ========== *args : Arguments which are operated """ if _sympify: args = tuple(map(_sympify_, args)) handlers = frozenset(map(self._handlergetter, args)) # no need to sympify again return self.dispatch(handlers)(*args, _sympify=False, **kwargs) @cacheit def dispatch(self, handlers): """ Select the handler class, and return its handler method. """ # Quick exit for the case where all handlers are same if len(handlers) == 1: h, = handlers if not isinstance(h, type): raise RuntimeError("Handler {!r} is not a type.".format(h)) return h # Recursively select with registered binary priority for i, typ in enumerate(handlers): if not isinstance(typ, type): raise RuntimeError("Handler {!r} is not a type.".format(typ)) if i == 0: handler = typ else: prev_handler = handler handler = self._dispatcher.dispatch(prev_handler, typ) if not isinstance(handler, type): raise RuntimeError( "Dispatcher for {!r} and {!r} must return a type, but got {!r}" .format(prev_handler, typ, handler)) # return handler class return handler @property def __doc__(self): docs = [ "Multiply dispatched associative operator: %s" % self.name, "Note that support for this is experimental, see the docs for :class:`AssocOpDispatcher` for details" ] if self.doc: docs.append(self.doc) s = "Registered handler classes\n" s += '=' * len(s) docs.append(s) amb_sigs = [] typ_sigs = defaultdict(list) for sigs in self._dispatcher.ordering[::-1]: key = self._dispatcher.funcs[sigs] typ_sigs[key].append(sigs) for typ, sigs in typ_sigs.items(): sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) if isinstance(typ, RaiseNotImplementedError): amb_sigs.append(sigs_str) continue s = 'Inputs: %s\n' % sigs_str s += '-' * len(s) + '\n' s += typ.__name__ docs.append(s) if amb_sigs: s = "Ambiguous handler classes\n" s += '=' * len(s) docs.append(s) s = '\n'.join(amb_sigs) docs.append(s) return '\n\n'.join(docs)
def test_raise_error_on_non_class(): f = Dispatcher('f') assert raises(TypeError, lambda: f.add((1, ), inc))
def test_union_types(): f = Dispatcher('f') f.register((int, float))(inc) assert f(1) == 2 assert f(1.0) == 2.0
def test_no_implementations(): f = Dispatcher("f") assert raises(NotImplementedError, lambda: f("hello"))
def __init__(self, name, commutative=False, doc=None): self.name = name self.doc = doc self.commutative = commutative self._dispatcher = Dispatcher(name)
def test_raise_error_on_non_class(): f = Dispatcher('f') assert raises(TypeError, lambda: f.add((1,), inc))
def test_no_implementations(): f = Dispatcher('f') assert raises(NotImplementedError, lambda: f('hello'))
def __new__(cls, clsname, bases, dct): if "handler" not in dct: name = f"Ask{clsname.capitalize()}Handler" handler = Dispatcher(name, doc="Handler for key %s" % name) dct["handler"] = handler return super().__new__(cls, clsname, bases, dct)