Coverage for /usr/lib/python3/dist-packages/sympy/printing/printer.py: 72%
105 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Printing subsystem driver
3SymPy's printing system works the following way: Any expression can be
4passed to a designated Printer who then is responsible to return an
5adequate representation of that expression.
7**The basic concept is the following:**
91. Let the object print itself if it knows how.
102. Take the best fitting method defined in the printer.
113. As fall-back use the emptyPrinter method for the printer.
13Which Method is Responsible for Printing?
14^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16The whole printing process is started by calling ``.doprint(expr)`` on the printer
17which you want to use. This method looks for an appropriate method which can
18print the given expression in the given style that the printer defines.
19While looking for the method, it follows these steps:
211. **Let the object print itself if it knows how.**
23 The printer looks for a specific method in every object. The name of that method
24 depends on the specific printer and is defined under ``Printer.printmethod``.
25 For example, StrPrinter calls ``_sympystr`` and LatexPrinter calls ``_latex``.
26 Look at the documentation of the printer that you want to use.
27 The name of the method is specified there.
29 This was the original way of doing printing in sympy. Every class had
30 its own latex, mathml, str and repr methods, but it turned out that it
31 is hard to produce a high quality printer, if all the methods are spread
32 out that far. Therefore all printing code was combined into the different
33 printers, which works great for built-in SymPy objects, but not that
34 good for user defined classes where it is inconvenient to patch the
35 printers.
372. **Take the best fitting method defined in the printer.**
39 The printer loops through expr classes (class + its bases), and tries
40 to dispatch the work to ``_print_<EXPR_CLASS>``
42 e.g., suppose we have the following class hierarchy::
44 Basic
45 |
46 Atom
47 |
48 Number
49 |
50 Rational
52 then, for ``expr=Rational(...)``, the Printer will try
53 to call printer methods in the order as shown in the figure below::
55 p._print(expr)
56 |
57 |-- p._print_Rational(expr)
58 |
59 |-- p._print_Number(expr)
60 |
61 |-- p._print_Atom(expr)
62 |
63 `-- p._print_Basic(expr)
65 if ``._print_Rational`` method exists in the printer, then it is called,
66 and the result is returned back. Otherwise, the printer tries to call
67 ``._print_Number`` and so on.
693. **As a fall-back use the emptyPrinter method for the printer.**
71 As fall-back ``self.emptyPrinter`` will be called with the expression. If
72 not defined in the Printer subclass this will be the same as ``str(expr)``.
74.. _printer_example:
76Example of Custom Printer
77^^^^^^^^^^^^^^^^^^^^^^^^^
79In the example below, we have a printer which prints the derivative of a function
80in a shorter form.
82.. code-block:: python
84 from sympy.core.symbol import Symbol
85 from sympy.printing.latex import LatexPrinter, print_latex
86 from sympy.core.function import UndefinedFunction, Function
89 class MyLatexPrinter(LatexPrinter):
90 \"\"\"Print derivative of a function of symbols in a shorter form.
91 \"\"\"
92 def _print_Derivative(self, expr):
93 function, *vars = expr.args
94 if not isinstance(type(function), UndefinedFunction) or \\
95 not all(isinstance(i, Symbol) for i in vars):
96 return super()._print_Derivative(expr)
98 # If you want the printer to work correctly for nested
99 # expressions then use self._print() instead of str() or latex().
100 # See the example of nested modulo below in the custom printing
101 # method section.
102 return "{}_{{{}}}".format(
103 self._print(Symbol(function.func.__name__)),
104 ''.join(self._print(i) for i in vars))
107 def print_my_latex(expr):
108 \"\"\" Most of the printers define their own wrappers for print().
109 These wrappers usually take printer settings. Our printer does not have
110 any settings.
111 \"\"\"
112 print(MyLatexPrinter().doprint(expr))
115 y = Symbol("y")
116 x = Symbol("x")
117 f = Function("f")
118 expr = f(x, y).diff(x, y)
120 # Print the expression using the normal latex printer and our custom
121 # printer.
122 print_latex(expr)
123 print_my_latex(expr)
125The output of the code above is::
127 \\frac{\\partial^{2}}{\\partial x\\partial y} f{\\left(x,y \\right)}
128 f_{xy}
130.. _printer_method_example:
132Example of Custom Printing Method
133^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
135In the example below, the latex printing of the modulo operator is modified.
136This is done by overriding the method ``_latex`` of ``Mod``.
138>>> from sympy import Symbol, Mod, Integer, print_latex
140>>> # Always use printer._print()
141>>> class ModOp(Mod):
142... def _latex(self, printer):
143... a, b = [printer._print(i) for i in self.args]
144... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
146Comparing the output of our custom operator to the builtin one:
148>>> x = Symbol('x')
149>>> m = Symbol('m')
150>>> print_latex(Mod(x, m))
151x \\bmod m
152>>> print_latex(ModOp(x, m))
153\\operatorname{Mod}{\\left(x, m\\right)}
155Common mistakes
156~~~~~~~~~~~~~~~
157It's important to always use ``self._print(obj)`` to print subcomponents of
158an expression when customizing a printer. Mistakes include:
1601. Using ``self.doprint(obj)`` instead:
162 >>> # This example does not work properly, as only the outermost call may use
163 >>> # doprint.
164 >>> class ModOpModeWrong(Mod):
165 ... def _latex(self, printer):
166 ... a, b = [printer.doprint(i) for i in self.args]
167 ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
169 This fails when the ``mode`` argument is passed to the printer:
171 >>> print_latex(ModOp(x, m), mode='inline') # ok
172 $\\operatorname{Mod}{\\left(x, m\\right)}$
173 >>> print_latex(ModOpModeWrong(x, m), mode='inline') # bad
174 $\\operatorname{Mod}{\\left($x$, $m$\\right)}$
1762. Using ``str(obj)`` instead:
178 >>> class ModOpNestedWrong(Mod):
179 ... def _latex(self, printer):
180 ... a, b = [str(i) for i in self.args]
181 ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
183 This fails on nested objects:
185 >>> # Nested modulo.
186 >>> print_latex(ModOp(ModOp(x, m), Integer(7))) # ok
187 \\operatorname{Mod}{\\left(\\operatorname{Mod}{\\left(x, m\\right)}, 7\\right)}
188 >>> print_latex(ModOpNestedWrong(ModOpNestedWrong(x, m), Integer(7))) # bad
189 \\operatorname{Mod}{\\left(ModOpNestedWrong(x, m), 7\\right)}
1913. Using ``LatexPrinter()._print(obj)`` instead.
193 >>> from sympy.printing.latex import LatexPrinter
194 >>> class ModOpSettingsWrong(Mod):
195 ... def _latex(self, printer):
196 ... a, b = [LatexPrinter()._print(i) for i in self.args]
197 ... return r"\\operatorname{Mod}{\\left(%s, %s\\right)}" % (a, b)
199 This causes all the settings to be discarded in the subobjects. As an
200 example, the ``full_prec`` setting which shows floats to full precision is
201 ignored:
203 >>> from sympy import Float
204 >>> print_latex(ModOp(Float(1) * x, m), full_prec=True) # ok
205 \\operatorname{Mod}{\\left(1.00000000000000 x, m\\right)}
206 >>> print_latex(ModOpSettingsWrong(Float(1) * x, m), full_prec=True) # bad
207 \\operatorname{Mod}{\\left(1.0 x, m\\right)}
209"""
211from __future__ import annotations
212import sys
213from typing import Any, Type
214import inspect
215from contextlib import contextmanager
216from functools import cmp_to_key, update_wrapper
218from sympy.core.add import Add
219from sympy.core.basic import Basic
221from sympy.core.function import AppliedUndef, UndefinedFunction, Function
225@contextmanager
226def printer_context(printer, **kwargs):
227 original = printer._context.copy()
228 try:
229 printer._context.update(kwargs)
230 yield
231 finally:
232 printer._context = original
235class Printer:
236 """ Generic printer
238 Its job is to provide infrastructure for implementing new printers easily.
240 If you want to define your custom Printer or your custom printing method
241 for your custom class then see the example above: printer_example_ .
242 """
244 _global_settings: dict[str, Any] = {}
246 _default_settings: dict[str, Any] = {}
248 printmethod = None # type: str
250 @classmethod
251 def _get_initial_settings(cls):
252 settings = cls._default_settings.copy()
253 for key, val in cls._global_settings.items():
254 if key in cls._default_settings:
255 settings[key] = val
256 return settings
258 def __init__(self, settings=None):
259 self._str = str
261 self._settings = self._get_initial_settings()
262 self._context = {} # mutable during printing
264 if settings is not None:
265 self._settings.update(settings)
267 if len(self._settings) > len(self._default_settings):
268 for key in self._settings:
269 if key not in self._default_settings:
270 raise TypeError("Unknown setting '%s'." % key)
272 # _print_level is the number of times self._print() was recursively
273 # called. See StrPrinter._print_Float() for an example of usage
274 self._print_level = 0
276 @classmethod
277 def set_global_settings(cls, **settings):
278 """Set system-wide printing settings. """
279 for key, val in settings.items():
280 if val is not None:
281 cls._global_settings[key] = val
283 @property
284 def order(self):
285 if 'order' in self._settings:
286 return self._settings['order']
287 else:
288 raise AttributeError("No order defined.")
290 def doprint(self, expr):
291 """Returns printer's representation for expr (as a string)"""
292 return self._str(self._print(expr))
294 def _print(self, expr, **kwargs) -> str:
295 """Internal dispatcher
297 Tries the following concepts to print an expression:
298 1. Let the object print itself if it knows how.
299 2. Take the best fitting method defined in the printer.
300 3. As fall-back use the emptyPrinter method for the printer.
301 """
302 self._print_level += 1
303 try:
304 # If the printer defines a name for a printing method
305 # (Printer.printmethod) and the object knows for itself how it
306 # should be printed, use that method.
307 if self.printmethod and hasattr(expr, self.printmethod):
308 if not (isinstance(expr, type) and issubclass(expr, Basic)):
309 return getattr(expr, self.printmethod)(self, **kwargs)
311 # See if the class of expr is known, or if one of its super
312 # classes is known, and use that print function
313 # Exception: ignore the subclasses of Undefined, so that, e.g.,
314 # Function('gamma') does not get dispatched to _print_gamma
315 classes = type(expr).__mro__
316 if AppliedUndef in classes:
317 classes = classes[classes.index(AppliedUndef):]
318 if UndefinedFunction in classes:
319 classes = classes[classes.index(UndefinedFunction):]
320 # Another exception: if someone subclasses a known function, e.g.,
321 # gamma, and changes the name, then ignore _print_gamma
322 if Function in classes:
323 i = classes.index(Function)
324 classes = tuple(c for c in classes[:i] if \
325 c.__name__ == classes[0].__name__ or \
326 c.__name__.endswith("Base")) + classes[i:]
327 for cls in classes:
328 printmethodname = '_print_' + cls.__name__
329 printmethod = getattr(self, printmethodname, None)
330 if printmethod is not None:
331 return printmethod(expr, **kwargs)
332 # Unknown object, fall back to the emptyPrinter.
333 return self.emptyPrinter(expr)
334 finally:
335 self._print_level -= 1
337 def emptyPrinter(self, expr):
338 return str(expr)
340 def _as_ordered_terms(self, expr, order=None):
341 """A compatibility function for ordering terms in Add. """
342 order = order or self.order
344 if order == 'old':
345 return sorted(Add.make_args(expr), key=cmp_to_key(Basic._compare_pretty))
346 elif order == 'none':
347 return list(expr.args)
348 else:
349 return expr.as_ordered_terms(order=order)
352class _PrintFunction:
353 """
354 Function wrapper to replace ``**settings`` in the signature with printer defaults
355 """
356 def __init__(self, f, print_cls: Type[Printer]):
357 # find all the non-setting arguments
358 params = list(inspect.signature(f).parameters.values())
359 assert params.pop(-1).kind == inspect.Parameter.VAR_KEYWORD
360 self.__other_params = params
362 # define the __globals__ attribute to fix Debian bug #980707
363 # which touches the package octave-symbolic
364 #
365 if hasattr(f, "__globals__"):
366 self.__globals__ = f.__globals__
367 else:
368 self.__globals__ = []
370 self.__print_cls = print_cls
371 update_wrapper(self, f)
373 def __reduce__(self):
374 # Since this is used as a decorator, it replaces the original function.
375 # The default pickling will try to pickle self.__wrapped__ and fail
376 # because the wrapped function can't be retrieved by name.
377 return self.__wrapped__.__qualname__
379 def __call__(self, *args, **kwargs):
380 return self.__wrapped__(*args, **kwargs)
382 @property
383 def __signature__(self) -> inspect.Signature:
384 settings = self.__print_cls._get_initial_settings()
385 return inspect.Signature(
386 parameters=self.__other_params + [
387 inspect.Parameter(k, inspect.Parameter.KEYWORD_ONLY, default=v)
388 for k, v in settings.items()
389 ],
390 return_annotation=self.__wrapped__.__annotations__.get('return', inspect.Signature.empty) # type:ignore
391 )
394def print_function(print_cls):
395 """ A decorator to replace kwargs with the printer settings in __signature__ """
396 def decorator(f):
397 if sys.version_info < (3, 9):
398 # We have to create a subclass so that `help` actually shows the docstring in older Python versions.
399 # IPython and Sphinx do not need this, only a raw Python console.
400 cls = type(f'{f.__qualname__}_PrintFunction', (_PrintFunction,), {"__doc__": f.__doc__})
401 else:
402 cls = _PrintFunction
403 return cls(f, print_cls)
404 return decorator