operations.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. from __future__ import annotations
  2. from typing import overload, TYPE_CHECKING
  3. from operator import attrgetter
  4. from collections import defaultdict
  5. from sympy.utilities.exceptions import sympy_deprecation_warning
  6. from .sympify import _sympify as _sympify_, sympify
  7. from .basic import Basic
  8. from .cache import cacheit
  9. from .sorting import ordered
  10. from .logic import fuzzy_and
  11. from .parameters import global_parameters
  12. from sympy.utilities.iterables import sift
  13. from sympy.multipledispatch.dispatcher import (Dispatcher,
  14. ambiguity_register_error_ignore_dup,
  15. str_signature, RaiseNotImplementedError)
  16. if TYPE_CHECKING:
  17. from sympy.core.expr import Expr
  18. from sympy.core.add import Add
  19. from sympy.core.mul import Mul
  20. from sympy.logic.boolalg import Boolean, And, Or
  21. class AssocOp(Basic):
  22. """ Associative operations, can separate noncommutative and
  23. commutative parts.
  24. (a op b) op c == a op (b op c) == a op b op c.
  25. Base class for Add and Mul.
  26. This is an abstract base class, concrete derived classes must define
  27. the attribute `identity`.
  28. .. deprecated:: 1.7
  29. Using arguments that aren't subclasses of :class:`~.Expr` in core
  30. operators (:class:`~.Mul`, :class:`~.Add`, and :class:`~.Pow`) is
  31. deprecated. See :ref:`non-expr-args-deprecated` for details.
  32. Parameters
  33. ==========
  34. *args :
  35. Arguments which are operated
  36. evaluate : bool, optional
  37. Evaluate the operation. If not passed, refer to ``global_parameters.evaluate``.
  38. """
  39. # for performance reason, we don't let is_commutative go to assumptions,
  40. # and keep it right here
  41. __slots__: tuple[str, ...] = ('is_commutative',)
  42. _args_type: type[Basic] | None = None
  43. @cacheit
  44. def __new__(cls, *args, evaluate=None, _sympify=True):
  45. # Allow faster processing by passing ``_sympify=False``, if all arguments
  46. # are already sympified.
  47. if _sympify:
  48. args = list(map(_sympify_, args))
  49. # Disallow non-Expr args in Add/Mul
  50. typ = cls._args_type
  51. if typ is not None:
  52. from .relational import Relational
  53. if any(isinstance(arg, Relational) for arg in args):
  54. raise TypeError("Relational cannot be used in %s" % cls.__name__)
  55. # This should raise TypeError once deprecation period is over:
  56. for arg in args:
  57. if not isinstance(arg, typ):
  58. sympy_deprecation_warning(
  59. f"""
  60. Using non-Expr arguments in {cls.__name__} is deprecated (in this case, one of
  61. the arguments has type {type(arg).__name__!r}).
  62. If you really did intend to use a multiplication or addition operation with
  63. this object, use the * or + operator instead.
  64. """,
  65. deprecated_since_version="1.7",
  66. active_deprecations_target="non-expr-args-deprecated",
  67. stacklevel=4,
  68. )
  69. if evaluate is None:
  70. evaluate = global_parameters.evaluate
  71. if not evaluate:
  72. obj = cls._from_args(args)
  73. obj = cls._exec_constructor_postprocessors(obj)
  74. return obj
  75. args = [a for a in args if a is not cls.identity]
  76. if len(args) == 0:
  77. return cls.identity
  78. if len(args) == 1:
  79. return args[0]
  80. c_part, nc_part, order_symbols = cls.flatten(args)
  81. is_commutative = not nc_part
  82. obj = cls._from_args(c_part + nc_part, is_commutative)
  83. obj = cls._exec_constructor_postprocessors(obj)
  84. if order_symbols is not None:
  85. from sympy.series.order import Order
  86. return Order(obj, *order_symbols)
  87. return obj
  88. @classmethod
  89. def _from_args(cls, args, is_commutative=None):
  90. """Create new instance with already-processed args.
  91. If the args are not in canonical order, then a non-canonical
  92. result will be returned, so use with caution. The order of
  93. args may change if the sign of the args is changed."""
  94. if len(args) == 0:
  95. return cls.identity
  96. elif len(args) == 1:
  97. return args[0]
  98. obj = super().__new__(cls, *args)
  99. if is_commutative is None:
  100. is_commutative = fuzzy_and(a.is_commutative for a in args)
  101. obj.is_commutative = is_commutative
  102. return obj
  103. def _new_rawargs(self, *args, reeval=True, **kwargs):
  104. """Create new instance of own class with args exactly as provided by
  105. caller but returning the self class identity if args is empty.
  106. Examples
  107. ========
  108. This is handy when we want to optimize things, e.g.
  109. >>> from sympy import Mul, S
  110. >>> from sympy.abc import x, y
  111. >>> e = Mul(3, x, y)
  112. >>> e.args
  113. (3, x, y)
  114. >>> Mul(*e.args[1:])
  115. x*y
  116. >>> e._new_rawargs(*e.args[1:]) # the same as above, but faster
  117. x*y
  118. Note: use this with caution. There is no checking of arguments at
  119. all. This is best used when you are rebuilding an Add or Mul after
  120. simply removing one or more args. If, for example, modifications,
  121. result in extra 1s being inserted they will show up in the result:
  122. >>> m = (x*y)._new_rawargs(S.One, x); m
  123. 1*x
  124. >>> m == x
  125. False
  126. >>> m.is_Mul
  127. True
  128. Another issue to be aware of is that the commutativity of the result
  129. is based on the commutativity of self. If you are rebuilding the
  130. terms that came from a commutative object then there will be no
  131. problem, but if self was non-commutative then what you are
  132. rebuilding may now be commutative.
  133. Although this routine tries to do as little as possible with the
  134. input, getting the commutativity right is important, so this level
  135. of safety is enforced: commutativity will always be recomputed if
  136. self is non-commutative and kwarg `reeval=False` has not been
  137. passed.
  138. """
  139. if reeval and self.is_commutative is False:
  140. is_commutative = None
  141. else:
  142. is_commutative = self.is_commutative
  143. return self._from_args(args, is_commutative)
  144. @classmethod
  145. def flatten(cls, seq):
  146. """Return seq so that none of the elements are of type `cls`. This is
  147. the vanilla routine that will be used if a class derived from AssocOp
  148. does not define its own flatten routine."""
  149. # apply associativity, no commutativity property is used
  150. new_seq = []
  151. while seq:
  152. o = seq.pop()
  153. if o.__class__ is cls: # classes must match exactly
  154. seq.extend(o.args)
  155. else:
  156. new_seq.append(o)
  157. new_seq.reverse()
  158. # c_part, nc_part, order_symbols
  159. return [], new_seq, None
  160. def _matches_commutative(self, expr, repl_dict=None, old=False):
  161. """
  162. Matches Add/Mul "pattern" to an expression "expr".
  163. repl_dict ... a dictionary of (wild: expression) pairs, that get
  164. returned with the results
  165. This function is the main workhorse for Add/Mul.
  166. Examples
  167. ========
  168. >>> from sympy import symbols, Wild, sin
  169. >>> a = Wild("a")
  170. >>> b = Wild("b")
  171. >>> c = Wild("c")
  172. >>> x, y, z = symbols("x y z")
  173. >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z)
  174. {a_: x, b_: y, c_: z}
  175. In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is
  176. the expression.
  177. The repl_dict contains parts that were already matched. For example
  178. here:
  179. >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x})
  180. {a_: x, b_: y, c_: z}
  181. the only function of the repl_dict is to return it in the
  182. result, e.g. if you omit it:
  183. >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z)
  184. {b_: y, c_: z}
  185. the "a: x" is not returned in the result, but otherwise it is
  186. equivalent.
  187. """
  188. from .function import _coeff_isneg
  189. # make sure expr is Expr if pattern is Expr
  190. from .expr import Expr
  191. if isinstance(self, Expr) and not isinstance(expr, Expr):
  192. return None
  193. if repl_dict is None:
  194. repl_dict = {}
  195. # handle simple patterns
  196. if self == expr:
  197. return repl_dict
  198. d = self._matches_simple(expr, repl_dict)
  199. if d is not None:
  200. return d
  201. # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2)
  202. from .function import WildFunction
  203. from .symbol import Wild
  204. wild_part, exact_part = sift(self.args, lambda p:
  205. p.has(Wild, WildFunction) and not expr.has(p),
  206. binary=True)
  207. if not exact_part:
  208. wild_part = list(ordered(wild_part))
  209. if self.is_Add:
  210. # in addition to normal ordered keys, impose
  211. # sorting on Muls with leading Number to put
  212. # them in order
  213. wild_part = sorted(wild_part, key=lambda x:
  214. x.args[0] if x.is_Mul and x.args[0].is_Number else
  215. 0)
  216. else:
  217. exact = self._new_rawargs(*exact_part)
  218. free = expr.free_symbols
  219. if free and (exact.free_symbols - free):
  220. # there are symbols in the exact part that are not
  221. # in the expr; but if there are no free symbols, let
  222. # the matching continue
  223. return None
  224. newexpr = self._combine_inverse(expr, exact)
  225. if not old and (expr.is_Add or expr.is_Mul):
  226. check = newexpr
  227. if _coeff_isneg(check):
  228. check = -check
  229. if check.count_ops() > expr.count_ops():
  230. return None
  231. newpattern = self._new_rawargs(*wild_part)
  232. return newpattern.matches(newexpr, repl_dict)
  233. # now to real work ;)
  234. i = 0
  235. saw = set()
  236. while expr not in saw:
  237. saw.add(expr)
  238. args = tuple(ordered(self.make_args(expr)))
  239. if self.is_Add and expr.is_Add:
  240. # in addition to normal ordered keys, impose
  241. # sorting on Muls with leading Number to put
  242. # them in order
  243. args = tuple(sorted(args, key=lambda x:
  244. x.args[0] if x.is_Mul and x.args[0].is_Number else
  245. 0))
  246. expr_list = (self.identity,) + args
  247. for last_op in reversed(expr_list):
  248. for w in reversed(wild_part):
  249. d1 = w.matches(last_op, repl_dict)
  250. if d1 is not None:
  251. d2 = self.xreplace(d1).matches(expr, d1)
  252. if d2 is not None:
  253. return d2
  254. if i == 0:
  255. if self.is_Mul:
  256. # make e**i look like Mul
  257. if expr.is_Pow and expr.exp.is_Integer:
  258. from .mul import Mul
  259. if expr.exp > 0:
  260. expr = Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False)
  261. else:
  262. expr = Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False)
  263. i += 1
  264. continue
  265. elif self.is_Add:
  266. # make i*e look like Add
  267. c, e = expr.as_coeff_Mul()
  268. if abs(c) > 1:
  269. from .add import Add
  270. if c > 0:
  271. expr = Add(*[e, (c - 1)*e], evaluate=False)
  272. else:
  273. expr = Add(*[-e, (c + 1)*e], evaluate=False)
  274. i += 1
  275. continue
  276. # try collection on non-Wild symbols
  277. from sympy.simplify.radsimp import collect
  278. was = expr
  279. did = set()
  280. for w in reversed(wild_part):
  281. c, w = w.as_coeff_mul(Wild)
  282. free = c.free_symbols - did
  283. if free:
  284. did.update(free)
  285. expr = collect(expr, free)
  286. if expr != was:
  287. i += 0
  288. continue
  289. break # if we didn't continue, there is nothing more to do
  290. return
  291. def _has_matcher(self):
  292. """Helper for .has() that checks for containment of
  293. subexpressions within an expr by using sets of args
  294. of similar nodes, e.g. x + 1 in x + y + 1 checks
  295. to see that {x, 1} & {x, y, 1} == {x, 1}
  296. """
  297. def _ncsplit(expr):
  298. # this is not the same as args_cnc because here
  299. # we don't assume expr is a Mul -- hence deal with args --
  300. # and always return a set.
  301. cpart, ncpart = sift(expr.args,
  302. lambda arg: arg.is_commutative is True, binary=True)
  303. return set(cpart), ncpart
  304. c, nc = _ncsplit(self)
  305. cls = self.__class__
  306. def is_in(expr):
  307. if isinstance(expr, cls):
  308. if expr == self:
  309. return True
  310. _c, _nc = _ncsplit(expr)
  311. if (c & _c) == c:
  312. if not nc:
  313. return True
  314. elif len(nc) <= len(_nc):
  315. for i in range(len(_nc) - len(nc) + 1):
  316. if _nc[i:i + len(nc)] == nc:
  317. return True
  318. return False
  319. return is_in
  320. def _eval_evalf(self, prec):
  321. """
  322. Evaluate the parts of self that are numbers; if the whole thing
  323. was a number with no functions it would have been evaluated, but
  324. it wasn't so we must judiciously extract the numbers and reconstruct
  325. the object. This is *not* simply replacing numbers with evaluated
  326. numbers. Numbers should be handled in the largest pure-number
  327. expression as possible. So the code below separates ``self`` into
  328. number and non-number parts and evaluates the number parts and
  329. walks the args of the non-number part recursively (doing the same
  330. thing).
  331. """
  332. from .add import Add
  333. from .mul import Mul
  334. from .symbol import Symbol
  335. from .function import AppliedUndef
  336. if isinstance(self, (Mul, Add)):
  337. x, tail = self.as_independent(Symbol, AppliedUndef)
  338. # if x is an AssocOp Function then the _evalf below will
  339. # call _eval_evalf (here) so we must break the recursion
  340. if not (tail is self.identity or
  341. isinstance(x, AssocOp) and x.is_Function or
  342. x is self.identity and isinstance(tail, AssocOp)):
  343. # here, we have a number so we just call to _evalf with prec;
  344. # prec is not the same as n, it is the binary precision so
  345. # that's why we don't call to evalf.
  346. x = x._evalf(prec) if x is not self.identity else self.identity
  347. args = []
  348. tail_args = tuple(self.func.make_args(tail))
  349. for a in tail_args:
  350. # here we call to _eval_evalf since we don't know what we
  351. # are dealing with and all other _eval_evalf routines should
  352. # be doing the same thing (i.e. taking binary prec and
  353. # finding the evalf-able args)
  354. newa = a._eval_evalf(prec)
  355. if newa is None:
  356. args.append(a)
  357. else:
  358. args.append(newa)
  359. return self.func(x, *args)
  360. # this is the same as above, but there were no pure-number args to
  361. # deal with
  362. args = []
  363. for a in self.args:
  364. newa = a._eval_evalf(prec)
  365. if newa is None:
  366. args.append(a)
  367. else:
  368. args.append(newa)
  369. return self.func(*args)
  370. @overload
  371. @classmethod
  372. def make_args(cls: type[Add], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore
  373. @overload
  374. @classmethod
  375. def make_args(cls: type[Mul], expr: Expr) -> tuple[Expr, ...]: ... # type: ignore
  376. @overload
  377. @classmethod
  378. def make_args(cls: type[And], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore
  379. @overload
  380. @classmethod
  381. def make_args(cls: type[Or], expr: Boolean) -> tuple[Boolean, ...]: ... # type: ignore
  382. @classmethod
  383. def make_args(cls: type[Basic], expr: Basic) -> tuple[Basic, ...]:
  384. """
  385. Return a sequence of elements `args` such that cls(*args) == expr
  386. Examples
  387. ========
  388. >>> from sympy import Symbol, Mul, Add
  389. >>> x, y = map(Symbol, 'xy')
  390. >>> Mul.make_args(x*y)
  391. (x, y)
  392. >>> Add.make_args(x*y)
  393. (x*y,)
  394. >>> set(Add.make_args(x*y + y)) == set([y, x*y])
  395. True
  396. """
  397. if isinstance(expr, cls):
  398. return expr.args
  399. else:
  400. return (sympify(expr),)
  401. def doit(self, **hints):
  402. if hints.get('deep', True):
  403. terms = [term.doit(**hints) for term in self.args]
  404. else:
  405. terms = self.args
  406. return self.func(*terms, evaluate=True)
  407. class ShortCircuit(Exception):
  408. pass
  409. class LatticeOp(AssocOp):
  410. """
  411. Join/meet operations of an algebraic lattice[1].
  412. Explanation
  413. ===========
  414. These binary operations are associative (op(op(a, b), c) = op(a, op(b, c))),
  415. commutative (op(a, b) = op(b, a)) and idempotent (op(a, a) = op(a) = a).
  416. Common examples are AND, OR, Union, Intersection, max or min. They have an
  417. identity element (op(identity, a) = a) and an absorbing element
  418. conventionally called zero (op(zero, a) = zero).
  419. This is an abstract base class, concrete derived classes must declare
  420. attributes zero and identity. All defining properties are then respected.
  421. Examples
  422. ========
  423. >>> from sympy import Integer
  424. >>> from sympy.core.operations import LatticeOp
  425. >>> class my_join(LatticeOp):
  426. ... zero = Integer(0)
  427. ... identity = Integer(1)
  428. >>> my_join(2, 3) == my_join(3, 2)
  429. True
  430. >>> my_join(2, my_join(3, 4)) == my_join(2, 3, 4)
  431. True
  432. >>> my_join(0, 1, 4, 2, 3, 4)
  433. 0
  434. >>> my_join(1, 2)
  435. 2
  436. References
  437. ==========
  438. .. [1] https://en.wikipedia.org/wiki/Lattice_%28order%29
  439. """
  440. is_commutative = True
  441. def __new__(cls, *args, **options):
  442. args = (_sympify_(arg) for arg in args)
  443. try:
  444. # /!\ args is a generator and _new_args_filter
  445. # must be careful to handle as such; this
  446. # is done so short-circuiting can be done
  447. # without having to sympify all values
  448. _args = frozenset(cls._new_args_filter(args))
  449. except ShortCircuit:
  450. return sympify(cls.zero)
  451. if not _args:
  452. return sympify(cls.identity)
  453. elif len(_args) == 1:
  454. return set(_args).pop()
  455. else:
  456. # XXX in almost every other case for __new__, *_args is
  457. # passed along, but the expectation here is for _args
  458. obj = super(AssocOp, cls).__new__(cls, *ordered(_args))
  459. obj._argset = _args
  460. return obj
  461. @classmethod
  462. def _new_args_filter(cls, arg_sequence, call_cls=None):
  463. """Generator filtering args"""
  464. ncls = call_cls or cls
  465. for arg in arg_sequence:
  466. if arg == ncls.zero:
  467. raise ShortCircuit(arg)
  468. elif arg == ncls.identity:
  469. continue
  470. elif arg.func == ncls:
  471. yield from arg.args
  472. else:
  473. yield arg
  474. @classmethod
  475. def make_args(cls, expr):
  476. """
  477. Return a set of args such that cls(*arg_set) == expr.
  478. """
  479. if isinstance(expr, cls):
  480. return expr._argset
  481. else:
  482. return frozenset([sympify(expr)])
  483. class AssocOpDispatcher:
  484. """
  485. Handler dispatcher for associative operators
  486. .. notes::
  487. This approach is experimental, and can be replaced or deleted in the future.
  488. See https://github.com/sympy/sympy/pull/19463.
  489. Explanation
  490. ===========
  491. If arguments of different types are passed, the classes which handle the operation for each type
  492. are collected. Then, a class which performs the operation is selected by recursive binary dispatching.
  493. Dispatching relation can be registered by ``register_handlerclass`` method.
  494. Priority registration is unordered. You cannot make ``A*B`` and ``B*A`` refer to
  495. different handler classes. All logic dealing with the order of arguments must be implemented
  496. in the handler class.
  497. Examples
  498. ========
  499. >>> from sympy import Add, Expr, Symbol
  500. >>> from sympy.core.add import add
  501. >>> class NewExpr(Expr):
  502. ... @property
  503. ... def _add_handler(self):
  504. ... return NewAdd
  505. >>> class NewAdd(NewExpr, Add):
  506. ... pass
  507. >>> add.register_handlerclass((Add, NewAdd), NewAdd)
  508. >>> a, b = Symbol('a'), NewExpr()
  509. >>> add(a, b) == NewAdd(a, b)
  510. True
  511. """
  512. def __init__(self, name, doc=None):
  513. self.name = name
  514. self.doc = doc
  515. self.handlerattr = "_%s_handler" % name
  516. self._handlergetter = attrgetter(self.handlerattr)
  517. self._dispatcher = Dispatcher(name)
  518. def __repr__(self):
  519. return "<dispatched %s>" % self.name
  520. def register_handlerclass(self, classes, typ, on_ambiguity=ambiguity_register_error_ignore_dup):
  521. """
  522. Register the handler class for two classes, in both straight and reversed order.
  523. Paramteters
  524. ===========
  525. classes : tuple of two types
  526. Classes who are compared with each other.
  527. typ:
  528. Class which is registered to represent *cls1* and *cls2*.
  529. Handler method of *self* must be implemented in this class.
  530. """
  531. if not len(classes) == 2:
  532. raise RuntimeError(
  533. "Only binary dispatch is supported, but got %s types: <%s>." % (
  534. len(classes), str_signature(classes)
  535. ))
  536. if len(set(classes)) == 1:
  537. raise RuntimeError(
  538. "Duplicate types <%s> cannot be dispatched." % str_signature(classes)
  539. )
  540. self._dispatcher.add(tuple(classes), typ, on_ambiguity=on_ambiguity)
  541. self._dispatcher.add(tuple(reversed(classes)), typ, on_ambiguity=on_ambiguity)
  542. @cacheit
  543. def __call__(self, *args, _sympify=True, **kwargs):
  544. """
  545. Parameters
  546. ==========
  547. *args :
  548. Arguments which are operated
  549. """
  550. if _sympify:
  551. args = tuple(map(_sympify_, args))
  552. handlers = frozenset(map(self._handlergetter, args))
  553. # no need to sympify again
  554. return self.dispatch(handlers)(*args, _sympify=False, **kwargs)
  555. @cacheit
  556. def dispatch(self, handlers):
  557. """
  558. Select the handler class, and return its handler method.
  559. """
  560. # Quick exit for the case where all handlers are same
  561. if len(handlers) == 1:
  562. h, = handlers
  563. if not isinstance(h, type):
  564. raise RuntimeError("Handler {!r} is not a type.".format(h))
  565. return h
  566. # Recursively select with registered binary priority
  567. for i, typ in enumerate(handlers):
  568. if not isinstance(typ, type):
  569. raise RuntimeError("Handler {!r} is not a type.".format(typ))
  570. if i == 0:
  571. handler = typ
  572. else:
  573. prev_handler = handler
  574. handler = self._dispatcher.dispatch(prev_handler, typ)
  575. if not isinstance(handler, type):
  576. raise RuntimeError(
  577. "Dispatcher for {!r} and {!r} must return a type, but got {!r}".format(
  578. prev_handler, typ, handler
  579. ))
  580. # return handler class
  581. return handler
  582. @property
  583. def __doc__(self):
  584. docs = [
  585. "Multiply dispatched associative operator: %s" % self.name,
  586. "Note that support for this is experimental, see the docs for :class:`AssocOpDispatcher` for details"
  587. ]
  588. if self.doc:
  589. docs.append(self.doc)
  590. s = "Registered handler classes\n"
  591. s += '=' * len(s)
  592. docs.append(s)
  593. amb_sigs = []
  594. typ_sigs = defaultdict(list)
  595. for sigs in self._dispatcher.ordering[::-1]:
  596. key = self._dispatcher.funcs[sigs]
  597. typ_sigs[key].append(sigs)
  598. for typ, sigs in typ_sigs.items():
  599. sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)
  600. if isinstance(typ, RaiseNotImplementedError):
  601. amb_sigs.append(sigs_str)
  602. continue
  603. s = 'Inputs: %s\n' % sigs_str
  604. s += '-' * len(s) + '\n'
  605. s += typ.__name__
  606. docs.append(s)
  607. if amb_sigs:
  608. s = "Ambiguous handler classes\n"
  609. s += '=' * len(s)
  610. docs.append(s)
  611. s = '\n'.join(amb_sigs)
  612. docs.append(s)
  613. return '\n\n'.join(docs)