Coverage for /usr/lib/python3/dist-packages/sympy/interactive/session.py: 8%
168 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"""Tools for setting up interactive sessions. """
3from sympy.external.gmpy import GROUND_TYPES
4from sympy.external.importtools import version_tuple
6from sympy.interactive.printing import init_printing
8from sympy.utilities.misc import ARCH
10preexec_source = """\
11from sympy import *
12x, y, z, t = symbols('x y z t')
13k, m, n = symbols('k m n', integer=True)
14f, g, h = symbols('f g h', cls=Function)
15init_printing()
16"""
18verbose_message = """\
19These commands were executed:
20%(source)s
21Documentation can be found at https://docs.sympy.org/%(version)s
22"""
24no_ipython = """\
25Could not locate IPython. Having IPython installed is greatly recommended.
26See http://ipython.scipy.org for more details. If you use Debian/Ubuntu,
27just install the 'ipython' package and start isympy again.
28"""
31def _make_message(ipython=True, quiet=False, source=None):
32 """Create a banner for an interactive session. """
33 from sympy import __version__ as sympy_version
34 from sympy import SYMPY_DEBUG
36 import sys
37 import os
39 if quiet:
40 return ""
42 python_version = "%d.%d.%d" % sys.version_info[:3]
44 if ipython:
45 shell_name = "IPython"
46 else:
47 shell_name = "Python"
49 info = ['ground types: %s' % GROUND_TYPES]
51 cache = os.getenv('SYMPY_USE_CACHE')
53 if cache is not None and cache.lower() == 'no':
54 info.append('cache: off')
56 if SYMPY_DEBUG:
57 info.append('debugging: on')
59 args = shell_name, sympy_version, python_version, ARCH, ', '.join(info)
60 message = "%s console for SymPy %s (Python %s-%s) (%s)\n" % args
62 if source is None:
63 source = preexec_source
65 _source = ""
67 for line in source.split('\n')[:-1]:
68 if not line:
69 _source += '\n'
70 else:
71 _source += '>>> ' + line + '\n'
73 doc_version = sympy_version
74 if 'dev' in doc_version:
75 doc_version = "dev"
76 else:
77 doc_version = "%s/" % doc_version
79 message += '\n' + verbose_message % {'source': _source,
80 'version': doc_version}
82 return message
85def int_to_Integer(s):
86 """
87 Wrap integer literals with Integer.
89 This is based on the decistmt example from
90 https://docs.python.org/3/library/tokenize.html.
92 Only integer literals are converted. Float literals are left alone.
94 Examples
95 ========
97 >>> from sympy import Integer # noqa: F401
98 >>> from sympy.interactive.session import int_to_Integer
99 >>> s = '1.2 + 1/2 - 0x12 + a1'
100 >>> int_to_Integer(s)
101 '1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 '
102 >>> s = 'print (1/2)'
103 >>> int_to_Integer(s)
104 'print (Integer (1 )/Integer (2 ))'
105 >>> exec(s)
106 0.5
107 >>> exec(int_to_Integer(s))
108 1/2
109 """
110 from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP
111 from io import StringIO
113 def _is_int(num):
114 """
115 Returns true if string value num (with token NUMBER) represents an integer.
116 """
117 # XXX: Is there something in the standard library that will do this?
118 if '.' in num or 'j' in num.lower() or 'e' in num.lower():
119 return False
120 return True
122 result = []
123 g = generate_tokens(StringIO(s).readline) # tokenize the string
124 for toknum, tokval, _, _, _ in g:
125 if toknum == NUMBER and _is_int(tokval): # replace NUMBER tokens
126 result.extend([
127 (NAME, 'Integer'),
128 (OP, '('),
129 (NUMBER, tokval),
130 (OP, ')')
131 ])
132 else:
133 result.append((toknum, tokval))
134 return untokenize(result)
137def enable_automatic_int_sympification(shell):
138 """
139 Allow IPython to automatically convert integer literals to Integer.
140 """
141 import ast
142 old_run_cell = shell.run_cell
144 def my_run_cell(cell, *args, **kwargs):
145 try:
146 # Check the cell for syntax errors. This way, the syntax error
147 # will show the original input, not the transformed input. The
148 # downside here is that IPython magic like %timeit will not work
149 # with transformed input (but on the other hand, IPython magic
150 # that doesn't expect transformed input will continue to work).
151 ast.parse(cell)
152 except SyntaxError:
153 pass
154 else:
155 cell = int_to_Integer(cell)
156 return old_run_cell(cell, *args, **kwargs)
158 shell.run_cell = my_run_cell
161def enable_automatic_symbols(shell):
162 """Allow IPython to automatically create symbols (``isympy -a``). """
163 # XXX: This should perhaps use tokenize, like int_to_Integer() above.
164 # This would avoid re-executing the code, which can lead to subtle
165 # issues. For example:
166 #
167 # In [1]: a = 1
168 #
169 # In [2]: for i in range(10):
170 # ...: a += 1
171 # ...:
172 #
173 # In [3]: a
174 # Out[3]: 11
175 #
176 # In [4]: a = 1
177 #
178 # In [5]: for i in range(10):
179 # ...: a += 1
180 # ...: print b
181 # ...:
182 # b
183 # b
184 # b
185 # b
186 # b
187 # b
188 # b
189 # b
190 # b
191 # b
192 #
193 # In [6]: a
194 # Out[6]: 12
195 #
196 # Note how the for loop is executed again because `b` was not defined, but `a`
197 # was already incremented once, so the result is that it is incremented
198 # multiple times.
200 import re
201 re_nameerror = re.compile(
202 "name '(?P<symbol>[A-Za-z_][A-Za-z0-9_]*)' is not defined")
204 def _handler(self, etype, value, tb, tb_offset=None):
205 """Handle :exc:`NameError` exception and allow injection of missing symbols. """
206 if etype is NameError and tb.tb_next and not tb.tb_next.tb_next:
207 match = re_nameerror.match(str(value))
209 if match is not None:
210 # XXX: Make sure Symbol is in scope. Otherwise you'll get infinite recursion.
211 self.run_cell("%(symbol)s = Symbol('%(symbol)s')" %
212 {'symbol': match.group("symbol")}, store_history=False)
214 try:
215 code = self.user_ns['In'][-1]
216 except (KeyError, IndexError):
217 pass
218 else:
219 self.run_cell(code, store_history=False)
220 return None
221 finally:
222 self.run_cell("del %s" % match.group("symbol"),
223 store_history=False)
225 stb = self.InteractiveTB.structured_traceback(
226 etype, value, tb, tb_offset=tb_offset)
227 self._showtraceback(etype, value, stb)
229 shell.set_custom_exc((NameError,), _handler)
232def init_ipython_session(shell=None, argv=[], auto_symbols=False, auto_int_to_Integer=False):
233 """Construct new IPython session. """
234 import IPython
236 if version_tuple(IPython.__version__) >= version_tuple('0.11'):
237 if not shell:
238 # use an app to parse the command line, and init config
239 # IPython 1.0 deprecates the frontend module, so we import directly
240 # from the terminal module to prevent a deprecation message from being
241 # shown.
242 if version_tuple(IPython.__version__) >= version_tuple('1.0'):
243 from IPython.terminal import ipapp
244 else:
245 from IPython.frontend.terminal import ipapp
246 app = ipapp.TerminalIPythonApp()
248 # don't draw IPython banner during initialization:
249 app.display_banner = False
250 app.initialize(argv)
252 shell = app.shell
254 if auto_symbols:
255 enable_automatic_symbols(shell)
256 if auto_int_to_Integer:
257 enable_automatic_int_sympification(shell)
259 return shell
260 else:
261 from IPython.Shell import make_IPython
262 return make_IPython(argv)
265def init_python_session():
266 """Construct new Python session. """
267 from code import InteractiveConsole
269 class SymPyConsole(InteractiveConsole):
270 """An interactive console with readline support. """
272 def __init__(self):
273 ns_locals = {}
274 InteractiveConsole.__init__(self, locals=ns_locals)
275 try:
276 import rlcompleter
277 import readline
278 except ImportError:
279 pass
280 else:
281 import os
282 import atexit
284 readline.set_completer(rlcompleter.Completer(ns_locals).complete)
285 readline.parse_and_bind('tab: complete')
287 if hasattr(readline, 'read_history_file'):
288 history = os.path.expanduser('~/.sympy-history')
290 try:
291 readline.read_history_file(history)
292 except OSError:
293 pass
295 atexit.register(readline.write_history_file, history)
297 return SymPyConsole()
300def init_session(ipython=None, pretty_print=True, order=None,
301 use_unicode=None, use_latex=None, quiet=False, auto_symbols=False,
302 auto_int_to_Integer=False, str_printer=None, pretty_printer=None,
303 latex_printer=None, argv=[]):
304 """
305 Initialize an embedded IPython or Python session. The IPython session is
306 initiated with the --pylab option, without the numpy imports, so that
307 matplotlib plotting can be interactive.
309 Parameters
310 ==========
312 pretty_print: boolean
313 If True, use pretty_print to stringify;
314 if False, use sstrrepr to stringify.
315 order: string or None
316 There are a few different settings for this parameter:
317 lex (default), which is lexographic order;
318 grlex, which is graded lexographic order;
319 grevlex, which is reversed graded lexographic order;
320 old, which is used for compatibility reasons and for long expressions;
321 None, which sets it to lex.
322 use_unicode: boolean or None
323 If True, use unicode characters;
324 if False, do not use unicode characters.
325 use_latex: boolean or None
326 If True, use latex rendering if IPython GUI's;
327 if False, do not use latex rendering.
328 quiet: boolean
329 If True, init_session will not print messages regarding its status;
330 if False, init_session will print messages regarding its status.
331 auto_symbols: boolean
332 If True, IPython will automatically create symbols for you.
333 If False, it will not.
334 The default is False.
335 auto_int_to_Integer: boolean
336 If True, IPython will automatically wrap int literals with Integer, so
337 that things like 1/2 give Rational(1, 2).
338 If False, it will not.
339 The default is False.
340 ipython: boolean or None
341 If True, printing will initialize for an IPython console;
342 if False, printing will initialize for a normal console;
343 The default is None, which automatically determines whether we are in
344 an ipython instance or not.
345 str_printer: function, optional, default=None
346 A custom string printer function. This should mimic
347 sympy.printing.sstrrepr().
348 pretty_printer: function, optional, default=None
349 A custom pretty printer. This should mimic sympy.printing.pretty().
350 latex_printer: function, optional, default=None
351 A custom LaTeX printer. This should mimic sympy.printing.latex()
352 This should mimic sympy.printing.latex().
353 argv: list of arguments for IPython
354 See sympy.bin.isympy for options that can be used to initialize IPython.
356 See Also
357 ========
359 sympy.interactive.printing.init_printing: for examples and the rest of the parameters.
362 Examples
363 ========
365 >>> from sympy import init_session, Symbol, sin, sqrt
366 >>> sin(x) #doctest: +SKIP
367 NameError: name 'x' is not defined
368 >>> init_session() #doctest: +SKIP
369 >>> sin(x) #doctest: +SKIP
370 sin(x)
371 >>> sqrt(5) #doctest: +SKIP
372 ___
373 \\/ 5
374 >>> init_session(pretty_print=False) #doctest: +SKIP
375 >>> sqrt(5) #doctest: +SKIP
376 sqrt(5)
377 >>> y + x + y**2 + x**2 #doctest: +SKIP
378 x**2 + x + y**2 + y
379 >>> init_session(order='grlex') #doctest: +SKIP
380 >>> y + x + y**2 + x**2 #doctest: +SKIP
381 x**2 + y**2 + x + y
382 >>> init_session(order='grevlex') #doctest: +SKIP
383 >>> y * x**2 + x * y**2 #doctest: +SKIP
384 x**2*y + x*y**2
385 >>> init_session(order='old') #doctest: +SKIP
386 >>> x**2 + y**2 + x + y #doctest: +SKIP
387 x + y + x**2 + y**2
388 >>> theta = Symbol('theta') #doctest: +SKIP
389 >>> theta #doctest: +SKIP
390 theta
391 >>> init_session(use_unicode=True) #doctest: +SKIP
392 >>> theta # doctest: +SKIP
393 \u03b8
394 """
395 import sys
397 in_ipython = False
399 if ipython is not False:
400 try:
401 import IPython
402 except ImportError:
403 if ipython is True:
404 raise RuntimeError("IPython is not available on this system")
405 ip = None
406 else:
407 try:
408 from IPython import get_ipython
409 ip = get_ipython()
410 except ImportError:
411 ip = None
412 in_ipython = bool(ip)
413 if ipython is None:
414 ipython = in_ipython
416 if ipython is False:
417 ip = init_python_session()
418 mainloop = ip.interact
419 else:
420 ip = init_ipython_session(ip, argv=argv, auto_symbols=auto_symbols,
421 auto_int_to_Integer=auto_int_to_Integer)
423 if version_tuple(IPython.__version__) >= version_tuple('0.11'):
424 # runsource is gone, use run_cell instead, which doesn't
425 # take a symbol arg. The second arg is `store_history`,
426 # and False means don't add the line to IPython's history.
427 ip.runsource = lambda src, symbol='exec': ip.run_cell(src, False)
429 # Enable interactive plotting using pylab.
430 try:
431 ip.enable_pylab(import_all=False)
432 except Exception:
433 # Causes an import error if matplotlib is not installed.
434 # Causes other errors (depending on the backend) if there
435 # is no display, or if there is some problem in the
436 # backend, so we have a bare "except Exception" here
437 pass
438 if not in_ipython:
439 mainloop = ip.mainloop
441 if auto_symbols and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')):
442 raise RuntimeError("automatic construction of symbols is possible only in IPython 0.11 or above")
443 if auto_int_to_Integer and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')):
444 raise RuntimeError("automatic int to Integer transformation is possible only in IPython 0.11 or above")
446 _preexec_source = preexec_source
448 ip.runsource(_preexec_source, symbol='exec')
449 init_printing(pretty_print=pretty_print, order=order,
450 use_unicode=use_unicode, use_latex=use_latex, ip=ip,
451 str_printer=str_printer, pretty_printer=pretty_printer,
452 latex_printer=latex_printer)
454 message = _make_message(ipython, quiet, _preexec_source)
456 if not in_ipython:
457 print(message)
458 mainloop()
459 sys.exit('Exiting ...')
460 else:
461 print(message)
462 import atexit
463 atexit.register(lambda: print("Exiting ...\n"))