Coverage for /usr/lib/python3/dist-packages/sympy/interactive/printing.py: 5%

234 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Tools for setting up printing in interactive sessions. """ 

2 

3from sympy.external.importtools import version_tuple 

4from io import BytesIO 

5 

6from sympy.printing.latex import latex as default_latex 

7from sympy.printing.preview import preview 

8from sympy.utilities.misc import debug 

9from sympy.printing.defaults import Printable 

10 

11 

12def _init_python_printing(stringify_func, **settings): 

13 """Setup printing in Python interactive session. """ 

14 import sys 

15 import builtins 

16 

17 def _displayhook(arg): 

18 """Python's pretty-printer display hook. 

19 

20 This function was adapted from: 

21 

22 https://www.python.org/dev/peps/pep-0217/ 

23 

24 """ 

25 if arg is not None: 

26 builtins._ = None 

27 print(stringify_func(arg, **settings)) 

28 builtins._ = arg 

29 

30 sys.displayhook = _displayhook 

31 

32 

33def _init_ipython_printing(ip, stringify_func, use_latex, euler, forecolor, 

34 backcolor, fontsize, latex_mode, print_builtin, 

35 latex_printer, scale, **settings): 

36 """Setup printing in IPython interactive session. """ 

37 try: 

38 from IPython.lib.latextools import latex_to_png 

39 except ImportError: 

40 pass 

41 

42 # Guess best font color if none was given based on the ip.colors string. 

43 # From the IPython documentation: 

44 # It has four case-insensitive values: 'nocolor', 'neutral', 'linux', 

45 # 'lightbg'. The default is neutral, which should be legible on either 

46 # dark or light terminal backgrounds. linux is optimised for dark 

47 # backgrounds and lightbg for light ones. 

48 if forecolor is None: 

49 color = ip.colors.lower() 

50 if color == 'lightbg': 

51 forecolor = 'Black' 

52 elif color == 'linux': 

53 forecolor = 'White' 

54 else: 

55 # No idea, go with gray. 

56 forecolor = 'Gray' 

57 debug("init_printing: Automatic foreground color:", forecolor) 

58 

59 if use_latex == "svg": 

60 extra_preamble = "\n\\special{color %s}" % forecolor 

61 else: 

62 extra_preamble = "" 

63 

64 imagesize = 'tight' 

65 offset = "0cm,0cm" 

66 resolution = round(150*scale) 

67 dvi = r"-T %s -D %d -bg %s -fg %s -O %s" % ( 

68 imagesize, resolution, backcolor, forecolor, offset) 

69 dvioptions = dvi.split() 

70 

71 svg_scale = 150/72*scale 

72 dvioptions_svg = ["--no-fonts", "--scale={}".format(svg_scale)] 

73 

74 debug("init_printing: DVIOPTIONS:", dvioptions) 

75 debug("init_printing: DVIOPTIONS_SVG:", dvioptions_svg) 

76 

77 latex = latex_printer or default_latex 

78 

79 def _print_plain(arg, p, cycle): 

80 """caller for pretty, for use in IPython 0.11""" 

81 if _can_print(arg): 

82 p.text(stringify_func(arg)) 

83 else: 

84 p.text(IPython.lib.pretty.pretty(arg)) 

85 

86 def _preview_wrapper(o): 

87 exprbuffer = BytesIO() 

88 try: 

89 preview(o, output='png', viewer='BytesIO', euler=euler, 

90 outputbuffer=exprbuffer, extra_preamble=extra_preamble, 

91 dvioptions=dvioptions, fontsize=fontsize) 

92 except Exception as e: 

93 # IPython swallows exceptions 

94 debug("png printing:", "_preview_wrapper exception raised:", 

95 repr(e)) 

96 raise 

97 return exprbuffer.getvalue() 

98 

99 def _svg_wrapper(o): 

100 exprbuffer = BytesIO() 

101 try: 

102 preview(o, output='svg', viewer='BytesIO', euler=euler, 

103 outputbuffer=exprbuffer, extra_preamble=extra_preamble, 

104 dvioptions=dvioptions_svg, fontsize=fontsize) 

105 except Exception as e: 

106 # IPython swallows exceptions 

107 debug("svg printing:", "_preview_wrapper exception raised:", 

108 repr(e)) 

109 raise 

110 return exprbuffer.getvalue().decode('utf-8') 

111 

112 def _matplotlib_wrapper(o): 

113 # mathtext can't render some LaTeX commands. For example, it can't 

114 # render any LaTeX environments such as array or matrix. So here we 

115 # ensure that if mathtext fails to render, we return None. 

116 try: 

117 try: 

118 return latex_to_png(o, color=forecolor, scale=scale) 

119 except TypeError: # Old IPython version without color and scale 

120 return latex_to_png(o) 

121 except ValueError as e: 

122 debug('matplotlib exception caught:', repr(e)) 

123 return None 

124 

125 

126 # Hook methods for builtin SymPy printers 

127 printing_hooks = ('_latex', '_sympystr', '_pretty', '_sympyrepr') 

128 

129 

130 def _can_print(o): 

131 """Return True if type o can be printed with one of the SymPy printers. 

132 

133 If o is a container type, this is True if and only if every element of 

134 o can be printed in this way. 

135 """ 

136 

137 try: 

138 # If you're adding another type, make sure you add it to printable_types 

139 # later in this file as well 

140 

141 builtin_types = (list, tuple, set, frozenset) 

142 if isinstance(o, builtin_types): 

143 # If the object is a custom subclass with a custom str or 

144 # repr, use that instead. 

145 if (type(o).__str__ not in (i.__str__ for i in builtin_types) or 

146 type(o).__repr__ not in (i.__repr__ for i in builtin_types)): 

147 return False 

148 return all(_can_print(i) for i in o) 

149 elif isinstance(o, dict): 

150 return all(_can_print(i) and _can_print(o[i]) for i in o) 

151 elif isinstance(o, bool): 

152 return False 

153 elif isinstance(o, Printable): 

154 # types known to SymPy 

155 return True 

156 elif any(hasattr(o, hook) for hook in printing_hooks): 

157 # types which add support themselves 

158 return True 

159 elif isinstance(o, (float, int)) and print_builtin: 

160 return True 

161 return False 

162 except RuntimeError: 

163 return False 

164 # This is in case maximum recursion depth is reached. 

165 # Since RecursionError is for versions of Python 3.5+ 

166 # so this is to guard against RecursionError for older versions. 

167 

168 def _print_latex_png(o): 

169 """ 

170 A function that returns a png rendered by an external latex 

171 distribution, falling back to matplotlib rendering 

172 """ 

173 if _can_print(o): 

174 s = latex(o, mode=latex_mode, **settings) 

175 if latex_mode == 'plain': 

176 s = '$\\displaystyle %s$' % s 

177 try: 

178 return _preview_wrapper(s) 

179 except RuntimeError as e: 

180 debug('preview failed with:', repr(e), 

181 ' Falling back to matplotlib backend') 

182 if latex_mode != 'inline': 

183 s = latex(o, mode='inline', **settings) 

184 return _matplotlib_wrapper(s) 

185 

186 def _print_latex_svg(o): 

187 """ 

188 A function that returns a svg rendered by an external latex 

189 distribution, no fallback available. 

190 """ 

191 if _can_print(o): 

192 s = latex(o, mode=latex_mode, **settings) 

193 if latex_mode == 'plain': 

194 s = '$\\displaystyle %s$' % s 

195 try: 

196 return _svg_wrapper(s) 

197 except RuntimeError as e: 

198 debug('preview failed with:', repr(e), 

199 ' No fallback available.') 

200 

201 def _print_latex_matplotlib(o): 

202 """ 

203 A function that returns a png rendered by mathtext 

204 """ 

205 if _can_print(o): 

206 s = latex(o, mode='inline', **settings) 

207 return _matplotlib_wrapper(s) 

208 

209 def _print_latex_text(o): 

210 """ 

211 A function to generate the latex representation of SymPy expressions. 

212 """ 

213 if _can_print(o): 

214 s = latex(o, mode=latex_mode, **settings) 

215 if latex_mode == 'plain': 

216 return '$\\displaystyle %s$' % s 

217 return s 

218 

219 def _result_display(self, arg): 

220 """IPython's pretty-printer display hook, for use in IPython 0.10 

221 

222 This function was adapted from: 

223 

224 ipython/IPython/hooks.py:155 

225 

226 """ 

227 if self.rc.pprint: 

228 out = stringify_func(arg) 

229 

230 if '\n' in out: 

231 print() 

232 

233 print(out) 

234 else: 

235 print(repr(arg)) 

236 

237 import IPython 

238 if version_tuple(IPython.__version__) >= version_tuple('0.11'): 

239 

240 # Printable is our own type, so we handle it with methods instead of 

241 # the approach required by builtin types. This allows downstream 

242 # packages to override the methods in their own subclasses of Printable, 

243 # which avoids the effects of gh-16002. 

244 printable_types = [float, tuple, list, set, frozenset, dict, int] 

245 

246 plaintext_formatter = ip.display_formatter.formatters['text/plain'] 

247 

248 # Exception to the rule above: IPython has better dispatching rules 

249 # for plaintext printing (xref ipython/ipython#8938), and we can't 

250 # use `_repr_pretty_` without hitting a recursion error in _print_plain. 

251 for cls in printable_types + [Printable]: 

252 plaintext_formatter.for_type(cls, _print_plain) 

253 

254 svg_formatter = ip.display_formatter.formatters['image/svg+xml'] 

255 if use_latex in ('svg', ): 

256 debug("init_printing: using svg formatter") 

257 for cls in printable_types: 

258 svg_formatter.for_type(cls, _print_latex_svg) 

259 Printable._repr_svg_ = _print_latex_svg 

260 else: 

261 debug("init_printing: not using any svg formatter") 

262 for cls in printable_types: 

263 # Better way to set this, but currently does not work in IPython 

264 #png_formatter.for_type(cls, None) 

265 if cls in svg_formatter.type_printers: 

266 svg_formatter.type_printers.pop(cls) 

267 Printable._repr_svg_ = Printable._repr_disabled 

268 

269 png_formatter = ip.display_formatter.formatters['image/png'] 

270 if use_latex in (True, 'png'): 

271 debug("init_printing: using png formatter") 

272 for cls in printable_types: 

273 png_formatter.for_type(cls, _print_latex_png) 

274 Printable._repr_png_ = _print_latex_png 

275 elif use_latex == 'matplotlib': 

276 debug("init_printing: using matplotlib formatter") 

277 for cls in printable_types: 

278 png_formatter.for_type(cls, _print_latex_matplotlib) 

279 Printable._repr_png_ = _print_latex_matplotlib 

280 else: 

281 debug("init_printing: not using any png formatter") 

282 for cls in printable_types: 

283 # Better way to set this, but currently does not work in IPython 

284 #png_formatter.for_type(cls, None) 

285 if cls in png_formatter.type_printers: 

286 png_formatter.type_printers.pop(cls) 

287 Printable._repr_png_ = Printable._repr_disabled 

288 

289 latex_formatter = ip.display_formatter.formatters['text/latex'] 

290 if use_latex in (True, 'mathjax'): 

291 debug("init_printing: using mathjax formatter") 

292 for cls in printable_types: 

293 latex_formatter.for_type(cls, _print_latex_text) 

294 Printable._repr_latex_ = _print_latex_text 

295 else: 

296 debug("init_printing: not using text/latex formatter") 

297 for cls in printable_types: 

298 # Better way to set this, but currently does not work in IPython 

299 #latex_formatter.for_type(cls, None) 

300 if cls in latex_formatter.type_printers: 

301 latex_formatter.type_printers.pop(cls) 

302 Printable._repr_latex_ = Printable._repr_disabled 

303 

304 else: 

305 ip.set_hook('result_display', _result_display) 

306 

307def _is_ipython(shell): 

308 """Is a shell instance an IPython shell?""" 

309 # shortcut, so we don't import IPython if we don't have to 

310 from sys import modules 

311 if 'IPython' not in modules: 

312 return False 

313 try: 

314 from IPython.core.interactiveshell import InteractiveShell 

315 except ImportError: 

316 # IPython < 0.11 

317 try: 

318 from IPython.iplib import InteractiveShell 

319 except ImportError: 

320 # Reaching this points means IPython has changed in a backward-incompatible way 

321 # that we don't know about. Warn? 

322 return False 

323 return isinstance(shell, InteractiveShell) 

324 

325# Used by the doctester to override the default for no_global 

326NO_GLOBAL = False 

327 

328def init_printing(pretty_print=True, order=None, use_unicode=None, 

329 use_latex=None, wrap_line=None, num_columns=None, 

330 no_global=False, ip=None, euler=False, forecolor=None, 

331 backcolor='Transparent', fontsize='10pt', 

332 latex_mode='plain', print_builtin=True, 

333 str_printer=None, pretty_printer=None, 

334 latex_printer=None, scale=1.0, **settings): 

335 r""" 

336 Initializes pretty-printer depending on the environment. 

337 

338 Parameters 

339 ========== 

340 

341 pretty_print : bool, default=True 

342 If ``True``, use :func:`~.pretty_print` to stringify or the provided pretty 

343 printer; if ``False``, use :func:`~.sstrrepr` to stringify or the provided string 

344 printer. 

345 order : string or None, default='lex' 

346 There are a few different settings for this parameter: 

347 ``'lex'`` (default), which is lexographic order; 

348 ``'grlex'``, which is graded lexographic order; 

349 ``'grevlex'``, which is reversed graded lexographic order; 

350 ``'old'``, which is used for compatibility reasons and for long expressions; 

351 ``None``, which sets it to lex. 

352 use_unicode : bool or None, default=None 

353 If ``True``, use unicode characters; 

354 if ``False``, do not use unicode characters; 

355 if ``None``, make a guess based on the environment. 

356 use_latex : string, bool, or None, default=None 

357 If ``True``, use default LaTeX rendering in GUI interfaces (png and 

358 mathjax); 

359 if ``False``, do not use LaTeX rendering; 

360 if ``None``, make a guess based on the environment; 

361 if ``'png'``, enable LaTeX rendering with an external LaTeX compiler, 

362 falling back to matplotlib if external compilation fails; 

363 if ``'matplotlib'``, enable LaTeX rendering with matplotlib; 

364 if ``'mathjax'``, enable LaTeX text generation, for example MathJax 

365 rendering in IPython notebook or text rendering in LaTeX documents; 

366 if ``'svg'``, enable LaTeX rendering with an external latex compiler, 

367 no fallback 

368 wrap_line : bool 

369 If True, lines will wrap at the end; if False, they will not wrap 

370 but continue as one line. This is only relevant if ``pretty_print`` is 

371 True. 

372 num_columns : int or None, default=None 

373 If ``int``, number of columns before wrapping is set to num_columns; if 

374 ``None``, number of columns before wrapping is set to terminal width. 

375 This is only relevant if ``pretty_print`` is ``True``. 

376 no_global : bool, default=False 

377 If ``True``, the settings become system wide; 

378 if ``False``, use just for this console/session. 

379 ip : An interactive console 

380 This can either be an instance of IPython, 

381 or a class that derives from code.InteractiveConsole. 

382 euler : bool, optional, default=False 

383 Loads the euler package in the LaTeX preamble for handwritten style 

384 fonts (https://www.ctan.org/pkg/euler). 

385 forecolor : string or None, optional, default=None 

386 DVI setting for foreground color. ``None`` means that either ``'Black'``, 

387 ``'White'``, or ``'Gray'`` will be selected based on a guess of the IPython 

388 terminal color setting. See notes. 

389 backcolor : string, optional, default='Transparent' 

390 DVI setting for background color. See notes. 

391 fontsize : string or int, optional, default='10pt' 

392 A font size to pass to the LaTeX documentclass function in the 

393 preamble. Note that the options are limited by the documentclass. 

394 Consider using scale instead. 

395 latex_mode : string, optional, default='plain' 

396 The mode used in the LaTeX printer. Can be one of: 

397 ``{'inline'|'plain'|'equation'|'equation*'}``. 

398 print_builtin : boolean, optional, default=True 

399 If ``True`` then floats and integers will be printed. If ``False`` the 

400 printer will only print SymPy types. 

401 str_printer : function, optional, default=None 

402 A custom string printer function. This should mimic 

403 :func:`~.sstrrepr()`. 

404 pretty_printer : function, optional, default=None 

405 A custom pretty printer. This should mimic :func:`~.pretty()`. 

406 latex_printer : function, optional, default=None 

407 A custom LaTeX printer. This should mimic :func:`~.latex()`. 

408 scale : float, optional, default=1.0 

409 Scale the LaTeX output when using the ``'png'`` or ``'svg'`` backends. 

410 Useful for high dpi screens. 

411 settings : 

412 Any additional settings for the ``latex`` and ``pretty`` commands can 

413 be used to fine-tune the output. 

414 

415 Examples 

416 ======== 

417 

418 >>> from sympy.interactive import init_printing 

419 >>> from sympy import Symbol, sqrt 

420 >>> from sympy.abc import x, y 

421 >>> sqrt(5) 

422 sqrt(5) 

423 >>> init_printing(pretty_print=True) # doctest: +SKIP 

424 >>> sqrt(5) # doctest: +SKIP 

425 ___ 

426 \/ 5 

427 >>> theta = Symbol('theta') # doctest: +SKIP 

428 >>> init_printing(use_unicode=True) # doctest: +SKIP 

429 >>> theta # doctest: +SKIP 

430 \u03b8 

431 >>> init_printing(use_unicode=False) # doctest: +SKIP 

432 >>> theta # doctest: +SKIP 

433 theta 

434 >>> init_printing(order='lex') # doctest: +SKIP 

435 >>> str(y + x + y**2 + x**2) # doctest: +SKIP 

436 x**2 + x + y**2 + y 

437 >>> init_printing(order='grlex') # doctest: +SKIP 

438 >>> str(y + x + y**2 + x**2) # doctest: +SKIP 

439 x**2 + x + y**2 + y 

440 >>> init_printing(order='grevlex') # doctest: +SKIP 

441 >>> str(y * x**2 + x * y**2) # doctest: +SKIP 

442 x**2*y + x*y**2 

443 >>> init_printing(order='old') # doctest: +SKIP 

444 >>> str(x**2 + y**2 + x + y) # doctest: +SKIP 

445 x**2 + x + y**2 + y 

446 >>> init_printing(num_columns=10) # doctest: +SKIP 

447 >>> x**2 + x + y**2 + y # doctest: +SKIP 

448 x + y + 

449 x**2 + y**2 

450 

451 Notes 

452 ===== 

453 

454 The foreground and background colors can be selected when using ``'png'`` or 

455 ``'svg'`` LaTeX rendering. Note that before the ``init_printing`` command is 

456 executed, the LaTeX rendering is handled by the IPython console and not SymPy. 

457 

458 The colors can be selected among the 68 standard colors known to ``dvips``, 

459 for a list see [1]_. In addition, the background color can be 

460 set to ``'Transparent'`` (which is the default value). 

461 

462 When using the ``'Auto'`` foreground color, the guess is based on the 

463 ``colors`` variable in the IPython console, see [2]_. Hence, if 

464 that variable is set correctly in your IPython console, there is a high 

465 chance that the output will be readable, although manual settings may be 

466 needed. 

467 

468 

469 References 

470 ========== 

471 

472 .. [1] https://en.wikibooks.org/wiki/LaTeX/Colors#The_68_standard_colors_known_to_dvips 

473 

474 .. [2] https://ipython.readthedocs.io/en/stable/config/details.html#terminal-colors 

475 

476 See Also 

477 ======== 

478 

479 sympy.printing.latex 

480 sympy.printing.pretty 

481 

482 """ 

483 import sys 

484 from sympy.printing.printer import Printer 

485 

486 if pretty_print: 

487 if pretty_printer is not None: 

488 stringify_func = pretty_printer 

489 else: 

490 from sympy.printing import pretty as stringify_func 

491 else: 

492 if str_printer is not None: 

493 stringify_func = str_printer 

494 else: 

495 from sympy.printing import sstrrepr as stringify_func 

496 

497 # Even if ip is not passed, double check that not in IPython shell 

498 in_ipython = False 

499 if ip is None: 

500 try: 

501 ip = get_ipython() 

502 except NameError: 

503 pass 

504 else: 

505 in_ipython = (ip is not None) 

506 

507 if ip and not in_ipython: 

508 in_ipython = _is_ipython(ip) 

509 

510 if in_ipython and pretty_print: 

511 try: 

512 import IPython 

513 # IPython 1.0 deprecates the frontend module, so we import directly 

514 # from the terminal module to prevent a deprecation message from being 

515 # shown. 

516 if version_tuple(IPython.__version__) >= version_tuple('1.0'): 

517 from IPython.terminal.interactiveshell import TerminalInteractiveShell 

518 else: 

519 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell 

520 from code import InteractiveConsole 

521 except ImportError: 

522 pass 

523 else: 

524 # This will be True if we are in the qtconsole or notebook 

525 if not isinstance(ip, (InteractiveConsole, TerminalInteractiveShell)) \ 

526 and 'ipython-console' not in ''.join(sys.argv): 

527 if use_unicode is None: 

528 debug("init_printing: Setting use_unicode to True") 

529 use_unicode = True 

530 if use_latex is None: 

531 debug("init_printing: Setting use_latex to True") 

532 use_latex = True 

533 

534 if not NO_GLOBAL and not no_global: 

535 Printer.set_global_settings(order=order, use_unicode=use_unicode, 

536 wrap_line=wrap_line, num_columns=num_columns) 

537 else: 

538 _stringify_func = stringify_func 

539 

540 if pretty_print: 

541 stringify_func = lambda expr, **settings: \ 

542 _stringify_func(expr, order=order, 

543 use_unicode=use_unicode, 

544 wrap_line=wrap_line, 

545 num_columns=num_columns, 

546 **settings) 

547 else: 

548 stringify_func = \ 

549 lambda expr, **settings: _stringify_func( 

550 expr, order=order, **settings) 

551 

552 if in_ipython: 

553 mode_in_settings = settings.pop("mode", None) 

554 if mode_in_settings: 

555 debug("init_printing: Mode is not able to be set due to internals" 

556 "of IPython printing") 

557 _init_ipython_printing(ip, stringify_func, use_latex, euler, 

558 forecolor, backcolor, fontsize, latex_mode, 

559 print_builtin, latex_printer, scale, 

560 **settings) 

561 else: 

562 _init_python_printing(stringify_func, **settings)