123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- """Printing subsystem driver
- SymPy's printing system works the following way: Any expression can be
- passed to a designated Printer who then is responsible to return an
- adequate representation of that expression.
- **The basic concept is the following:**
- 1. Let the object print itself if it knows how.
- 2. Take the best fitting method defined in the printer.
- 3. As fall-back use the emptyPrinter method for the printer.
- Which Method is Responsible for Printing?
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- The whole printing process is started by calling ``.doprint(expr)`` on the printer
- which you want to use. This method looks for an appropriate method which can
- print the given expression in the given style that the printer defines.
- While looking for the method, it follows these steps:
- 1. **Let the object print itself if it knows how.**
- The printer looks for a specific method in every object. The name of that method
- depends on the specific printer and is defined under ``Printer.printmethod``.
- For example, StrPrinter calls ``_sympystr`` and LatexPrinter calls ``_latex``.
- Look at the documentation of the printer that you want to use.
- The name of the method is specified there.
- This was the original way of doing printing in sympy. Every class had
- its own latex, mathml, str and repr methods, but it turned out that it
- is hard to produce a high quality printer, if all the methods are spread
- out that far. Therefore all printing code was combined into the different
- printers, which works great for built-in SymPy objects, but not that
- good for user defined classes where it is inconvenient to patch the
- printers.
- 2. **Take the best fitting method defined in the printer.**
- The printer loops through expr classes (class + its bases), and tries
- to dispatch the work to ``_print_<EXPR_CLASS>``
- e.g., suppose we have the following class hierarchy::
- Basic
- |
- Atom
- |
- Number
- |
- Rational
- then, for ``expr=Rational(...)``, the Printer will try
- to call printer methods in the order as shown in the figure below::
- p._print(expr)
- |
- |-- p._print_Rational(expr)
- |
- |-- p._print_Number(expr)
- |
- |-- p._print_Atom(expr)
- |
- `-- p._print_Basic(expr)
- if ``._print_Rational`` method exists in the printer, then it is called,
- and the result is returned back. Otherwise, the printer tries to call
- ``._print_Number`` and so on.
- 3. **As a fall-back use the emptyPrinter method for the printer.**
- As fall-back ``self.emptyPrinter`` will be called with the expression. If
- not defined in the Printer subclass this will be the same as ``str(expr)``.
- .. _printer_example:
- Example of Custom Printer
- ^^^^^^^^^^^^^^^^^^^^^^^^^
- In the example below, we have a printer which prints the derivative of a function
- in a shorter form.
- .. code-block:: python
- from sympy.core.symbol import Symbol
- from sympy.printing.latex import LatexPrinter, print_latex
- from sympy.core.function import UndefinedFunction, Function
- class MyLatexPrinter(LatexPrinter):
- \"\"\"Print derivative of a function of symbols in a shorter form.
- \"\"\"
- def _print_Derivative(self, expr):
- function, *vars = expr.args
- if not isinstance(type(function), UndefinedFunction) or \\
- not all(isinstance(i, Symbol) for i in vars):
- return super()._print_Derivative(expr)
- # If you want the printer to work correctly for nested
- # expressions then use self._print() instead of str() or latex().
- # See the example of nested modulo below in the custom printing
- # method section.
- return "{}_{{{}}}".format(
- self._print(Symbol(function.func.__name__)),
- ''.join(self._print(i) for i in vars))
- def print_my_latex(expr):
- \"\"\" Most of the printers define their own wrappers for print().
- These wrappers usually take printer settings. Our printer does not have
- any settings.
- \"\"\"
- print(MyLatexPrinter().doprint(expr))
- y = Symbol("y")
- x = Symbol("x")
- f = Function("f")
- expr = f(x, y).diff(x, y)
- # Print the expression using the normal latex printer and our custom
- # printer.
- print_latex(expr)
- print_my_latex(expr)
- The output of the code above is::
- \\frac{\\partial^{2}}{\\partial x\\partial y} f{\\left(x,y \\right)}
- f_{xy}
- .. _printer_method_example:
- Example of Custom Printing Method
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- In the example below, the latex printing of the modulo operator is modified.
- This is done by overriding the method ``_latex`` of ``Mod``.
- >>> from sympy import Symbol, Mod, Integer, print_latex
- >>> # Always use printer._print()
- >>> class ModOp(Mod):
- ... def _latex(self, printer):
- ... a, b = [printer._print(i) for i in self.args]
- ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
- Comparing the output of our custom operator to the builtin one:
- >>> x = Symbol('x')
- >>> m = Symbol('m')
- >>> print_latex(Mod(x, m))
- x \\bmod m
- >>> print_latex(ModOp(x, m))
- \\operatorname{Mod}{\\left(x, m\\right)}
- Common mistakes
- ~~~~~~~~~~~~~~~
- It's important to always use ``self._print(obj)`` to print subcomponents of
- an expression when customizing a printer. Mistakes include:
- 1. Using ``self.doprint(obj)`` instead:
- >>> # This example does not work properly, as only the outermost call may use
- >>> # doprint.
- >>> class ModOpModeWrong(Mod):
- ... def _latex(self, printer):
- ... a, b = [printer.doprint(i) for i in self.args]
- ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
- This fails when the `mode` argument is passed to the printer:
- >>> print_latex(ModOp(x, m), mode='inline') # ok
- $\\operatorname{Mod}{\\left(x, m\\right)}$
- >>> print_latex(ModOpModeWrong(x, m), mode='inline') # bad
- $\\operatorname{Mod}{\\left($x$, $m$\\right)}$
- 2. Using ``str(obj)`` instead:
- >>> class ModOpNestedWrong(Mod):
- ... def _latex(self, printer):
- ... a, b = [str(i) for i in self.args]
- ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
- This fails on nested objects:
- >>> # Nested modulo.
- >>> print_latex(ModOp(ModOp(x, m), Integer(7))) # ok
- \\operatorname{Mod}{\\left(\\operatorname{Mod}{\\left(x, m\\right)}, 7\\right)}
- >>> print_latex(ModOpNestedWrong(ModOpNestedWrong(x, m), Integer(7))) # bad
- \\operatorname{Mod}{\\left(ModOpNestedWrong(x, m), 7\\right)}
- 3. Using ``LatexPrinter()._print(obj)`` instead.
- >>> from sympy.printing.latex import LatexPrinter
- >>> class ModOpSettingsWrong(Mod):
- ... def _latex(self, printer):
- ... a, b = [LatexPrinter()._print(i) for i in self.args]
- ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
- This causes all the settings to be discarded in the subobjects. As an
- example, the ``full_prec`` setting which shows floats to full precision is
- ignored:
- >>> from sympy import Float
- >>> print_latex(ModOp(Float(1) * x, m), full_prec=True) # ok
- \\operatorname{Mod}{\\left(1.00000000000000 x, m\\right)}
- >>> print_latex(ModOpSettingsWrong(Float(1) * x, m), full_prec=True) # bad
- \\operatorname{Mod}{\\left(1.0 x, m\\right)}
- """
- import sys
- from typing import Any, Dict as tDict, Type
- import inspect
- from contextlib import contextmanager
- from functools import cmp_to_key, update_wrapper
- from sympy.core.add import Add
- from sympy.core.basic import Basic
- from sympy.core.core import BasicMeta
- from sympy.core.function import AppliedUndef, UndefinedFunction, Function
- @contextmanager
- def printer_context(printer, **kwargs):
- original = printer._context.copy()
- try:
- printer._context.update(kwargs)
- yield
- finally:
- printer._context = original
- class Printer:
- """ Generic printer
- Its job is to provide infrastructure for implementing new printers easily.
- If you want to define your custom Printer or your custom printing method
- for your custom class then see the example above: printer_example_ .
- """
- _global_settings = {} # type: tDict[str, Any]
- _default_settings = {} # type: tDict[str, Any]
- printmethod = None # type: str
- @classmethod
- def _get_initial_settings(cls):
- settings = cls._default_settings.copy()
- for key, val in cls._global_settings.items():
- if key in cls._default_settings:
- settings[key] = val
- return settings
- def __init__(self, settings=None):
- self._str = str
- self._settings = self._get_initial_settings()
- self._context = dict() # mutable during printing
- if settings is not None:
- self._settings.update(settings)
- if len(self._settings) > len(self._default_settings):
- for key in self._settings:
- if key not in self._default_settings:
- raise TypeError("Unknown setting '%s'." % key)
- # _print_level is the number of times self._print() was recursively
- # called. See StrPrinter._print_Float() for an example of usage
- self._print_level = 0
- @classmethod
- def set_global_settings(cls, **settings):
- """Set system-wide printing settings. """
- for key, val in settings.items():
- if val is not None:
- cls._global_settings[key] = val
- @property
- def order(self):
- if 'order' in self._settings:
- return self._settings['order']
- else:
- raise AttributeError("No order defined.")
- def doprint(self, expr):
- """Returns printer's representation for expr (as a string)"""
- return self._str(self._print(expr))
- def _print(self, expr, **kwargs):
- """Internal dispatcher
- Tries the following concepts to print an expression:
- 1. Let the object print itself if it knows how.
- 2. Take the best fitting method defined in the printer.
- 3. As fall-back use the emptyPrinter method for the printer.
- """
- self._print_level += 1
- try:
- # If the printer defines a name for a printing method
- # (Printer.printmethod) and the object knows for itself how it
- # should be printed, use that method.
- if (self.printmethod and hasattr(expr, self.printmethod)
- and not isinstance(expr, BasicMeta)):
- return getattr(expr, self.printmethod)(self, **kwargs)
- # See if the class of expr is known, or if one of its super
- # classes is known, and use that print function
- # Exception: ignore the subclasses of Undefined, so that, e.g.,
- # Function('gamma') does not get dispatched to _print_gamma
- classes = type(expr).__mro__
- if AppliedUndef in classes:
- classes = classes[classes.index(AppliedUndef):]
- if UndefinedFunction in classes:
- classes = classes[classes.index(UndefinedFunction):]
- # Another exception: if someone subclasses a known function, e.g.,
- # gamma, and changes the name, then ignore _print_gamma
- if Function in classes:
- i = classes.index(Function)
- classes = tuple(c for c in classes[:i] if \
- c.__name__ == classes[0].__name__ or \
- c.__name__.endswith("Base")) + classes[i:]
- for cls in classes:
- printmethodname = '_print_' + cls.__name__
- printmethod = getattr(self, printmethodname, None)
- if printmethod is not None:
- return printmethod(expr, **kwargs)
- # Unknown object, fall back to the emptyPrinter.
- return self.emptyPrinter(expr)
- finally:
- self._print_level -= 1
- def emptyPrinter(self, expr):
- return str(expr)
- def _as_ordered_terms(self, expr, order=None):
- """A compatibility function for ordering terms in Add. """
- order = order or self.order
- if order == 'old':
- return sorted(Add.make_args(expr), key=cmp_to_key(Basic._compare_pretty))
- elif order == 'none':
- return list(expr.args)
- else:
- return expr.as_ordered_terms(order=order)
- class _PrintFunction:
- """
- Function wrapper to replace ``**settings`` in the signature with printer defaults
- """
- def __init__(self, f, print_cls: Type[Printer]):
- # find all the non-setting arguments
- params = list(inspect.signature(f).parameters.values())
- assert params.pop(-1).kind == inspect.Parameter.VAR_KEYWORD
- self.__other_params = params
- self.__print_cls = print_cls
- update_wrapper(self, f)
- def __reduce__(self):
- # Since this is used as a decorator, it replaces the original function.
- # The default pickling will try to pickle self.__wrapped__ and fail
- # because the wrapped function can't be retrieved by name.
- return self.__wrapped__.__qualname__
- def __call__(self, *args, **kwargs):
- return self.__wrapped__(*args, **kwargs)
- @property
- def __signature__(self) -> inspect.Signature:
- settings = self.__print_cls._get_initial_settings()
- return inspect.Signature(
- parameters=self.__other_params + [
- inspect.Parameter(k, inspect.Parameter.KEYWORD_ONLY, default=v)
- for k, v in settings.items()
- ],
- return_annotation=self.__wrapped__.__annotations__.get('return', inspect.Signature.empty) # type:ignore
- )
- def print_function(print_cls):
- """ A decorator to replace kwargs with the printer settings in __signature__ """
- def decorator(f):
- if sys.version_info < (3, 9):
- # We have to create a subclass so that `help` actually shows the docstring in older Python versions.
- # IPython and Sphinx do not need this, only a raw Python console.
- cls = type(f'{f.__qualname__}_PrintFunction', (_PrintFunction,), dict(__doc__=f.__doc__))
- else:
- cls = _PrintFunction
- return cls(f, print_cls)
- return decorator
|