563 lines
22 KiB
Python
563 lines
22 KiB
Python
"""Tools for setting up printing in interactive sessions. """
|
|
|
|
from sympy.external.importtools import version_tuple
|
|
from io import BytesIO
|
|
|
|
from sympy.printing.latex import latex as default_latex
|
|
from sympy.printing.preview import preview
|
|
from sympy.utilities.misc import debug
|
|
from sympy.printing.defaults import Printable
|
|
|
|
|
|
def _init_python_printing(stringify_func, **settings):
|
|
"""Setup printing in Python interactive session. """
|
|
import sys
|
|
import builtins
|
|
|
|
def _displayhook(arg):
|
|
"""Python's pretty-printer display hook.
|
|
|
|
This function was adapted from:
|
|
|
|
https://www.python.org/dev/peps/pep-0217/
|
|
|
|
"""
|
|
if arg is not None:
|
|
builtins._ = None
|
|
print(stringify_func(arg, **settings))
|
|
builtins._ = arg
|
|
|
|
sys.displayhook = _displayhook
|
|
|
|
|
|
def _init_ipython_printing(ip, stringify_func, use_latex, euler, forecolor,
|
|
backcolor, fontsize, latex_mode, print_builtin,
|
|
latex_printer, scale, **settings):
|
|
"""Setup printing in IPython interactive session. """
|
|
try:
|
|
from IPython.lib.latextools import latex_to_png
|
|
except ImportError:
|
|
pass
|
|
|
|
# Guess best font color if none was given based on the ip.colors string.
|
|
# From the IPython documentation:
|
|
# It has four case-insensitive values: 'nocolor', 'neutral', 'linux',
|
|
# 'lightbg'. The default is neutral, which should be legible on either
|
|
# dark or light terminal backgrounds. linux is optimised for dark
|
|
# backgrounds and lightbg for light ones.
|
|
if forecolor is None:
|
|
color = ip.colors.lower()
|
|
if color == 'lightbg':
|
|
forecolor = 'Black'
|
|
elif color == 'linux':
|
|
forecolor = 'White'
|
|
else:
|
|
# No idea, go with gray.
|
|
forecolor = 'Gray'
|
|
debug("init_printing: Automatic foreground color:", forecolor)
|
|
|
|
if use_latex == "svg":
|
|
extra_preamble = "\n\\special{color %s}" % forecolor
|
|
else:
|
|
extra_preamble = ""
|
|
|
|
imagesize = 'tight'
|
|
offset = "0cm,0cm"
|
|
resolution = round(150*scale)
|
|
dvi = r"-T %s -D %d -bg %s -fg %s -O %s" % (
|
|
imagesize, resolution, backcolor, forecolor, offset)
|
|
dvioptions = dvi.split()
|
|
|
|
svg_scale = 150/72*scale
|
|
dvioptions_svg = ["--no-fonts", "--scale={}".format(svg_scale)]
|
|
|
|
debug("init_printing: DVIOPTIONS:", dvioptions)
|
|
debug("init_printing: DVIOPTIONS_SVG:", dvioptions_svg)
|
|
|
|
latex = latex_printer or default_latex
|
|
|
|
def _print_plain(arg, p, cycle):
|
|
"""caller for pretty, for use in IPython 0.11"""
|
|
if _can_print(arg):
|
|
p.text(stringify_func(arg))
|
|
else:
|
|
p.text(IPython.lib.pretty.pretty(arg))
|
|
|
|
def _preview_wrapper(o):
|
|
exprbuffer = BytesIO()
|
|
try:
|
|
preview(o, output='png', viewer='BytesIO', euler=euler,
|
|
outputbuffer=exprbuffer, extra_preamble=extra_preamble,
|
|
dvioptions=dvioptions, fontsize=fontsize)
|
|
except Exception as e:
|
|
# IPython swallows exceptions
|
|
debug("png printing:", "_preview_wrapper exception raised:",
|
|
repr(e))
|
|
raise
|
|
return exprbuffer.getvalue()
|
|
|
|
def _svg_wrapper(o):
|
|
exprbuffer = BytesIO()
|
|
try:
|
|
preview(o, output='svg', viewer='BytesIO', euler=euler,
|
|
outputbuffer=exprbuffer, extra_preamble=extra_preamble,
|
|
dvioptions=dvioptions_svg, fontsize=fontsize)
|
|
except Exception as e:
|
|
# IPython swallows exceptions
|
|
debug("svg printing:", "_preview_wrapper exception raised:",
|
|
repr(e))
|
|
raise
|
|
return exprbuffer.getvalue().decode('utf-8')
|
|
|
|
def _matplotlib_wrapper(o):
|
|
# mathtext can't render some LaTeX commands. For example, it can't
|
|
# render any LaTeX environments such as array or matrix. So here we
|
|
# ensure that if mathtext fails to render, we return None.
|
|
try:
|
|
try:
|
|
return latex_to_png(o, color=forecolor, scale=scale)
|
|
except TypeError: # Old IPython version without color and scale
|
|
return latex_to_png(o)
|
|
except ValueError as e:
|
|
debug('matplotlib exception caught:', repr(e))
|
|
return None
|
|
|
|
|
|
# Hook methods for builtin SymPy printers
|
|
printing_hooks = ('_latex', '_sympystr', '_pretty', '_sympyrepr')
|
|
|
|
|
|
def _can_print(o):
|
|
"""Return True if type o can be printed with one of the SymPy printers.
|
|
|
|
If o is a container type, this is True if and only if every element of
|
|
o can be printed in this way.
|
|
"""
|
|
|
|
try:
|
|
# If you're adding another type, make sure you add it to printable_types
|
|
# later in this file as well
|
|
|
|
builtin_types = (list, tuple, set, frozenset)
|
|
if isinstance(o, builtin_types):
|
|
# If the object is a custom subclass with a custom str or
|
|
# repr, use that instead.
|
|
if (type(o).__str__ not in (i.__str__ for i in builtin_types) or
|
|
type(o).__repr__ not in (i.__repr__ for i in builtin_types)):
|
|
return False
|
|
return all(_can_print(i) for i in o)
|
|
elif isinstance(o, dict):
|
|
return all(_can_print(i) and _can_print(o[i]) for i in o)
|
|
elif isinstance(o, bool):
|
|
return False
|
|
elif isinstance(o, Printable):
|
|
# types known to SymPy
|
|
return True
|
|
elif any(hasattr(o, hook) for hook in printing_hooks):
|
|
# types which add support themselves
|
|
return True
|
|
elif isinstance(o, (float, int)) and print_builtin:
|
|
return True
|
|
return False
|
|
except RuntimeError:
|
|
return False
|
|
# This is in case maximum recursion depth is reached.
|
|
# Since RecursionError is for versions of Python 3.5+
|
|
# so this is to guard against RecursionError for older versions.
|
|
|
|
def _print_latex_png(o):
|
|
"""
|
|
A function that returns a png rendered by an external latex
|
|
distribution, falling back to matplotlib rendering
|
|
"""
|
|
if _can_print(o):
|
|
s = latex(o, mode=latex_mode, **settings)
|
|
if latex_mode == 'plain':
|
|
s = '$\\displaystyle %s$' % s
|
|
try:
|
|
return _preview_wrapper(s)
|
|
except RuntimeError as e:
|
|
debug('preview failed with:', repr(e),
|
|
' Falling back to matplotlib backend')
|
|
if latex_mode != 'inline':
|
|
s = latex(o, mode='inline', **settings)
|
|
return _matplotlib_wrapper(s)
|
|
|
|
def _print_latex_svg(o):
|
|
"""
|
|
A function that returns a svg rendered by an external latex
|
|
distribution, no fallback available.
|
|
"""
|
|
if _can_print(o):
|
|
s = latex(o, mode=latex_mode, **settings)
|
|
if latex_mode == 'plain':
|
|
s = '$\\displaystyle %s$' % s
|
|
try:
|
|
return _svg_wrapper(s)
|
|
except RuntimeError as e:
|
|
debug('preview failed with:', repr(e),
|
|
' No fallback available.')
|
|
|
|
def _print_latex_matplotlib(o):
|
|
"""
|
|
A function that returns a png rendered by mathtext
|
|
"""
|
|
if _can_print(o):
|
|
s = latex(o, mode='inline', **settings)
|
|
return _matplotlib_wrapper(s)
|
|
|
|
def _print_latex_text(o):
|
|
"""
|
|
A function to generate the latex representation of SymPy expressions.
|
|
"""
|
|
if _can_print(o):
|
|
s = latex(o, mode=latex_mode, **settings)
|
|
if latex_mode == 'plain':
|
|
return '$\\displaystyle %s$' % s
|
|
return s
|
|
|
|
def _result_display(self, arg):
|
|
"""IPython's pretty-printer display hook, for use in IPython 0.10
|
|
|
|
This function was adapted from:
|
|
|
|
ipython/IPython/hooks.py:155
|
|
|
|
"""
|
|
if self.rc.pprint:
|
|
out = stringify_func(arg)
|
|
|
|
if '\n' in out:
|
|
print()
|
|
|
|
print(out)
|
|
else:
|
|
print(repr(arg))
|
|
|
|
import IPython
|
|
if version_tuple(IPython.__version__) >= version_tuple('0.11'):
|
|
|
|
# Printable is our own type, so we handle it with methods instead of
|
|
# the approach required by builtin types. This allows downstream
|
|
# packages to override the methods in their own subclasses of Printable,
|
|
# which avoids the effects of gh-16002.
|
|
printable_types = [float, tuple, list, set, frozenset, dict, int]
|
|
|
|
plaintext_formatter = ip.display_formatter.formatters['text/plain']
|
|
|
|
# Exception to the rule above: IPython has better dispatching rules
|
|
# for plaintext printing (xref ipython/ipython#8938), and we can't
|
|
# use `_repr_pretty_` without hitting a recursion error in _print_plain.
|
|
for cls in printable_types + [Printable]:
|
|
plaintext_formatter.for_type(cls, _print_plain)
|
|
|
|
svg_formatter = ip.display_formatter.formatters['image/svg+xml']
|
|
if use_latex in ('svg', ):
|
|
debug("init_printing: using svg formatter")
|
|
for cls in printable_types:
|
|
svg_formatter.for_type(cls, _print_latex_svg)
|
|
Printable._repr_svg_ = _print_latex_svg
|
|
else:
|
|
debug("init_printing: not using any svg formatter")
|
|
for cls in printable_types:
|
|
# Better way to set this, but currently does not work in IPython
|
|
#png_formatter.for_type(cls, None)
|
|
if cls in svg_formatter.type_printers:
|
|
svg_formatter.type_printers.pop(cls)
|
|
Printable._repr_svg_ = Printable._repr_disabled
|
|
|
|
png_formatter = ip.display_formatter.formatters['image/png']
|
|
if use_latex in (True, 'png'):
|
|
debug("init_printing: using png formatter")
|
|
for cls in printable_types:
|
|
png_formatter.for_type(cls, _print_latex_png)
|
|
Printable._repr_png_ = _print_latex_png
|
|
elif use_latex == 'matplotlib':
|
|
debug("init_printing: using matplotlib formatter")
|
|
for cls in printable_types:
|
|
png_formatter.for_type(cls, _print_latex_matplotlib)
|
|
Printable._repr_png_ = _print_latex_matplotlib
|
|
else:
|
|
debug("init_printing: not using any png formatter")
|
|
for cls in printable_types:
|
|
# Better way to set this, but currently does not work in IPython
|
|
#png_formatter.for_type(cls, None)
|
|
if cls in png_formatter.type_printers:
|
|
png_formatter.type_printers.pop(cls)
|
|
Printable._repr_png_ = Printable._repr_disabled
|
|
|
|
latex_formatter = ip.display_formatter.formatters['text/latex']
|
|
if use_latex in (True, 'mathjax'):
|
|
debug("init_printing: using mathjax formatter")
|
|
for cls in printable_types:
|
|
latex_formatter.for_type(cls, _print_latex_text)
|
|
Printable._repr_latex_ = _print_latex_text
|
|
else:
|
|
debug("init_printing: not using text/latex formatter")
|
|
for cls in printable_types:
|
|
# Better way to set this, but currently does not work in IPython
|
|
#latex_formatter.for_type(cls, None)
|
|
if cls in latex_formatter.type_printers:
|
|
latex_formatter.type_printers.pop(cls)
|
|
Printable._repr_latex_ = Printable._repr_disabled
|
|
|
|
else:
|
|
ip.set_hook('result_display', _result_display)
|
|
|
|
def _is_ipython(shell):
|
|
"""Is a shell instance an IPython shell?"""
|
|
# shortcut, so we don't import IPython if we don't have to
|
|
from sys import modules
|
|
if 'IPython' not in modules:
|
|
return False
|
|
try:
|
|
from IPython.core.interactiveshell import InteractiveShell
|
|
except ImportError:
|
|
# IPython < 0.11
|
|
try:
|
|
from IPython.iplib import InteractiveShell
|
|
except ImportError:
|
|
# Reaching this points means IPython has changed in a backward-incompatible way
|
|
# that we don't know about. Warn?
|
|
return False
|
|
return isinstance(shell, InteractiveShell)
|
|
|
|
# Used by the doctester to override the default for no_global
|
|
NO_GLOBAL = False
|
|
|
|
def init_printing(pretty_print=True, order=None, use_unicode=None,
|
|
use_latex=None, wrap_line=None, num_columns=None,
|
|
no_global=False, ip=None, euler=False, forecolor=None,
|
|
backcolor='Transparent', fontsize='10pt',
|
|
latex_mode='plain', print_builtin=True,
|
|
str_printer=None, pretty_printer=None,
|
|
latex_printer=None, scale=1.0, **settings):
|
|
r"""
|
|
Initializes pretty-printer depending on the environment.
|
|
|
|
Parameters
|
|
==========
|
|
|
|
pretty_print : bool, default=True
|
|
If ``True``, use :func:`~.pretty_print` to stringify or the provided pretty
|
|
printer; if ``False``, use :func:`~.sstrrepr` to stringify or the provided string
|
|
printer.
|
|
order : string or None, default='lex'
|
|
There are a few different settings for this parameter:
|
|
``'lex'`` (default), which is lexographic order;
|
|
``'grlex'``, which is graded lexographic order;
|
|
``'grevlex'``, which is reversed graded lexographic order;
|
|
``'old'``, which is used for compatibility reasons and for long expressions;
|
|
``None``, which sets it to lex.
|
|
use_unicode : bool or None, default=None
|
|
If ``True``, use unicode characters;
|
|
if ``False``, do not use unicode characters;
|
|
if ``None``, make a guess based on the environment.
|
|
use_latex : string, bool, or None, default=None
|
|
If ``True``, use default LaTeX rendering in GUI interfaces (png and
|
|
mathjax);
|
|
if ``False``, do not use LaTeX rendering;
|
|
if ``None``, make a guess based on the environment;
|
|
if ``'png'``, enable LaTeX rendering with an external LaTeX compiler,
|
|
falling back to matplotlib if external compilation fails;
|
|
if ``'matplotlib'``, enable LaTeX rendering with matplotlib;
|
|
if ``'mathjax'``, enable LaTeX text generation, for example MathJax
|
|
rendering in IPython notebook or text rendering in LaTeX documents;
|
|
if ``'svg'``, enable LaTeX rendering with an external latex compiler,
|
|
no fallback
|
|
wrap_line : bool
|
|
If True, lines will wrap at the end; if False, they will not wrap
|
|
but continue as one line. This is only relevant if ``pretty_print`` is
|
|
True.
|
|
num_columns : int or None, default=None
|
|
If ``int``, number of columns before wrapping is set to num_columns; if
|
|
``None``, number of columns before wrapping is set to terminal width.
|
|
This is only relevant if ``pretty_print`` is ``True``.
|
|
no_global : bool, default=False
|
|
If ``True``, the settings become system wide;
|
|
if ``False``, use just for this console/session.
|
|
ip : An interactive console
|
|
This can either be an instance of IPython,
|
|
or a class that derives from code.InteractiveConsole.
|
|
euler : bool, optional, default=False
|
|
Loads the euler package in the LaTeX preamble for handwritten style
|
|
fonts (https://www.ctan.org/pkg/euler).
|
|
forecolor : string or None, optional, default=None
|
|
DVI setting for foreground color. ``None`` means that either ``'Black'``,
|
|
``'White'``, or ``'Gray'`` will be selected based on a guess of the IPython
|
|
terminal color setting. See notes.
|
|
backcolor : string, optional, default='Transparent'
|
|
DVI setting for background color. See notes.
|
|
fontsize : string or int, optional, default='10pt'
|
|
A font size to pass to the LaTeX documentclass function in the
|
|
preamble. Note that the options are limited by the documentclass.
|
|
Consider using scale instead.
|
|
latex_mode : string, optional, default='plain'
|
|
The mode used in the LaTeX printer. Can be one of:
|
|
``{'inline'|'plain'|'equation'|'equation*'}``.
|
|
print_builtin : boolean, optional, default=True
|
|
If ``True`` then floats and integers will be printed. If ``False`` the
|
|
printer will only print SymPy types.
|
|
str_printer : function, optional, default=None
|
|
A custom string printer function. This should mimic
|
|
:func:`~.sstrrepr()`.
|
|
pretty_printer : function, optional, default=None
|
|
A custom pretty printer. This should mimic :func:`~.pretty()`.
|
|
latex_printer : function, optional, default=None
|
|
A custom LaTeX printer. This should mimic :func:`~.latex()`.
|
|
scale : float, optional, default=1.0
|
|
Scale the LaTeX output when using the ``'png'`` or ``'svg'`` backends.
|
|
Useful for high dpi screens.
|
|
settings :
|
|
Any additional settings for the ``latex`` and ``pretty`` commands can
|
|
be used to fine-tune the output.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.interactive import init_printing
|
|
>>> from sympy import Symbol, sqrt
|
|
>>> from sympy.abc import x, y
|
|
>>> sqrt(5)
|
|
sqrt(5)
|
|
>>> init_printing(pretty_print=True) # doctest: +SKIP
|
|
>>> sqrt(5) # doctest: +SKIP
|
|
___
|
|
\/ 5
|
|
>>> theta = Symbol('theta') # doctest: +SKIP
|
|
>>> init_printing(use_unicode=True) # doctest: +SKIP
|
|
>>> theta # doctest: +SKIP
|
|
\u03b8
|
|
>>> init_printing(use_unicode=False) # doctest: +SKIP
|
|
>>> theta # doctest: +SKIP
|
|
theta
|
|
>>> init_printing(order='lex') # doctest: +SKIP
|
|
>>> str(y + x + y**2 + x**2) # doctest: +SKIP
|
|
x**2 + x + y**2 + y
|
|
>>> init_printing(order='grlex') # doctest: +SKIP
|
|
>>> str(y + x + y**2 + x**2) # doctest: +SKIP
|
|
x**2 + x + y**2 + y
|
|
>>> init_printing(order='grevlex') # doctest: +SKIP
|
|
>>> str(y * x**2 + x * y**2) # doctest: +SKIP
|
|
x**2*y + x*y**2
|
|
>>> init_printing(order='old') # doctest: +SKIP
|
|
>>> str(x**2 + y**2 + x + y) # doctest: +SKIP
|
|
x**2 + x + y**2 + y
|
|
>>> init_printing(num_columns=10) # doctest: +SKIP
|
|
>>> x**2 + x + y**2 + y # doctest: +SKIP
|
|
x + y +
|
|
x**2 + y**2
|
|
|
|
Notes
|
|
=====
|
|
|
|
The foreground and background colors can be selected when using ``'png'`` or
|
|
``'svg'`` LaTeX rendering. Note that before the ``init_printing`` command is
|
|
executed, the LaTeX rendering is handled by the IPython console and not SymPy.
|
|
|
|
The colors can be selected among the 68 standard colors known to ``dvips``,
|
|
for a list see [1]_. In addition, the background color can be
|
|
set to ``'Transparent'`` (which is the default value).
|
|
|
|
When using the ``'Auto'`` foreground color, the guess is based on the
|
|
``colors`` variable in the IPython console, see [2]_. Hence, if
|
|
that variable is set correctly in your IPython console, there is a high
|
|
chance that the output will be readable, although manual settings may be
|
|
needed.
|
|
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://en.wikibooks.org/wiki/LaTeX/Colors#The_68_standard_colors_known_to_dvips
|
|
|
|
.. [2] https://ipython.readthedocs.io/en/stable/config/details.html#terminal-colors
|
|
|
|
See Also
|
|
========
|
|
|
|
sympy.printing.latex
|
|
sympy.printing.pretty
|
|
|
|
"""
|
|
import sys
|
|
from sympy.printing.printer import Printer
|
|
|
|
if pretty_print:
|
|
if pretty_printer is not None:
|
|
stringify_func = pretty_printer
|
|
else:
|
|
from sympy.printing import pretty as stringify_func
|
|
else:
|
|
if str_printer is not None:
|
|
stringify_func = str_printer
|
|
else:
|
|
from sympy.printing import sstrrepr as stringify_func
|
|
|
|
# Even if ip is not passed, double check that not in IPython shell
|
|
in_ipython = False
|
|
if ip is None:
|
|
try:
|
|
ip = get_ipython()
|
|
except NameError:
|
|
pass
|
|
else:
|
|
in_ipython = (ip is not None)
|
|
|
|
if ip and not in_ipython:
|
|
in_ipython = _is_ipython(ip)
|
|
|
|
if in_ipython and pretty_print:
|
|
try:
|
|
import IPython
|
|
# IPython 1.0 deprecates the frontend module, so we import directly
|
|
# from the terminal module to prevent a deprecation message from being
|
|
# shown.
|
|
if version_tuple(IPython.__version__) >= version_tuple('1.0'):
|
|
from IPython.terminal.interactiveshell import TerminalInteractiveShell
|
|
else:
|
|
from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
|
|
from code import InteractiveConsole
|
|
except ImportError:
|
|
pass
|
|
else:
|
|
# This will be True if we are in the qtconsole or notebook
|
|
if not isinstance(ip, (InteractiveConsole, TerminalInteractiveShell)) \
|
|
and 'ipython-console' not in ''.join(sys.argv):
|
|
if use_unicode is None:
|
|
debug("init_printing: Setting use_unicode to True")
|
|
use_unicode = True
|
|
if use_latex is None:
|
|
debug("init_printing: Setting use_latex to True")
|
|
use_latex = True
|
|
|
|
if not NO_GLOBAL and not no_global:
|
|
Printer.set_global_settings(order=order, use_unicode=use_unicode,
|
|
wrap_line=wrap_line, num_columns=num_columns)
|
|
else:
|
|
_stringify_func = stringify_func
|
|
|
|
if pretty_print:
|
|
stringify_func = lambda expr, **settings: \
|
|
_stringify_func(expr, order=order,
|
|
use_unicode=use_unicode,
|
|
wrap_line=wrap_line,
|
|
num_columns=num_columns,
|
|
**settings)
|
|
else:
|
|
stringify_func = \
|
|
lambda expr, **settings: _stringify_func(
|
|
expr, order=order, **settings)
|
|
|
|
if in_ipython:
|
|
mode_in_settings = settings.pop("mode", None)
|
|
if mode_in_settings:
|
|
debug("init_printing: Mode is not able to be set due to internals"
|
|
"of IPython printing")
|
|
_init_ipython_printing(ip, stringify_func, use_latex, euler,
|
|
forecolor, backcolor, fontsize, latex_mode,
|
|
print_builtin, latex_printer, scale,
|
|
**settings)
|
|
else:
|
|
_init_python_printing(stringify_func, **settings)
|