"""Tools for setting up interactive sessions. """ from sympy.external.gmpy import GROUND_TYPES from sympy.external.importtools import version_tuple from sympy.interactive.printing import init_printing from sympy.utilities.misc import ARCH preexec_source = """\ from sympy import * x, y, z, t = symbols('x y z t') k, m, n = symbols('k m n', integer=True) f, g, h = symbols('f g h', cls=Function) init_printing() """ verbose_message = """\ These commands were executed: %(source)s Documentation can be found at https://docs.sympy.org/%(version)s """ no_ipython = """\ Couldn't locate IPython. Having IPython installed is greatly recommended. See http://ipython.scipy.org for more details. If you use Debian/Ubuntu, just install the 'ipython' package and start isympy again. """ def _make_message(ipython=True, quiet=False, source=None): """Create a banner for an interactive session. """ from sympy import __version__ as sympy_version from sympy import SYMPY_DEBUG import sys import os if quiet: return "" python_version = "%d.%d.%d" % sys.version_info[:3] if ipython: shell_name = "IPython" else: shell_name = "Python" info = ['ground types: %s' % GROUND_TYPES] cache = os.getenv('SYMPY_USE_CACHE') if cache is not None and cache.lower() == 'no': info.append('cache: off') if SYMPY_DEBUG: info.append('debugging: on') args = shell_name, sympy_version, python_version, ARCH, ', '.join(info) message = "%s console for SymPy %s (Python %s-%s) (%s)\n" % args if source is None: source = preexec_source _source = "" for line in source.split('\n')[:-1]: if not line: _source += '\n' else: _source += '>>> ' + line + '\n' doc_version = sympy_version if 'dev' in doc_version: doc_version = "dev" else: doc_version = "%s/" % doc_version message += '\n' + verbose_message % {'source': _source, 'version': doc_version} return message def int_to_Integer(s): """ Wrap integer literals with Integer. This is based on the decistmt example from http://docs.python.org/library/tokenize.html. Only integer literals are converted. Float literals are left alone. Examples ======== >>> from sympy import Integer # noqa: F401 >>> from sympy.interactive.session import int_to_Integer >>> s = '1.2 + 1/2 - 0x12 + a1' >>> int_to_Integer(s) '1.2 +Integer (1 )/Integer (2 )-Integer (0x12 )+a1 ' >>> s = 'print (1/2)' >>> int_to_Integer(s) 'print (Integer (1 )/Integer (2 ))' >>> exec(s) 0.5 >>> exec(int_to_Integer(s)) 1/2 """ from tokenize import generate_tokens, untokenize, NUMBER, NAME, OP from io import StringIO def _is_int(num): """ Returns true if string value num (with token NUMBER) represents an integer. """ # XXX: Is there something in the standard library that will do this? if '.' in num or 'j' in num.lower() or 'e' in num.lower(): return False return True result = [] g = generate_tokens(StringIO(s).readline) # tokenize the string for toknum, tokval, _, _, _ in g: if toknum == NUMBER and _is_int(tokval): # replace NUMBER tokens result.extend([ (NAME, 'Integer'), (OP, '('), (NUMBER, tokval), (OP, ')') ]) else: result.append((toknum, tokval)) return untokenize(result) def enable_automatic_int_sympification(shell): """ Allow IPython to automatically convert integer literals to Integer. """ import ast old_run_cell = shell.run_cell def my_run_cell(cell, *args, **kwargs): try: # Check the cell for syntax errors. This way, the syntax error # will show the original input, not the transformed input. The # downside here is that IPython magic like %timeit will not work # with transformed input (but on the other hand, IPython magic # that doesn't expect transformed input will continue to work). ast.parse(cell) except SyntaxError: pass else: cell = int_to_Integer(cell) old_run_cell(cell, *args, **kwargs) shell.run_cell = my_run_cell def enable_automatic_symbols(shell): """Allow IPython to automatically create symbols (``isympy -a``). """ # XXX: This should perhaps use tokenize, like int_to_Integer() above. # This would avoid re-executing the code, which can lead to subtle # issues. For example: # # In [1]: a = 1 # # In [2]: for i in range(10): # ...: a += 1 # ...: # # In [3]: a # Out[3]: 11 # # In [4]: a = 1 # # In [5]: for i in range(10): # ...: a += 1 # ...: print b # ...: # b # b # b # b # b # b # b # b # b # b # # In [6]: a # Out[6]: 12 # # Note how the for loop is executed again because `b` was not defined, but `a` # was already incremented once, so the result is that it is incremented # multiple times. import re re_nameerror = re.compile( "name '(?P[A-Za-z_][A-Za-z0-9_]*)' is not defined") def _handler(self, etype, value, tb, tb_offset=None): """Handle :exc:`NameError` exception and allow injection of missing symbols. """ if etype is NameError and tb.tb_next and not tb.tb_next.tb_next: match = re_nameerror.match(str(value)) if match is not None: # XXX: Make sure Symbol is in scope. Otherwise you'll get infinite recursion. self.run_cell("%(symbol)s = Symbol('%(symbol)s')" % {'symbol': match.group("symbol")}, store_history=False) try: code = self.user_ns['In'][-1] except (KeyError, IndexError): pass else: self.run_cell(code, store_history=False) return None finally: self.run_cell("del %s" % match.group("symbol"), store_history=False) stb = self.InteractiveTB.structured_traceback( etype, value, tb, tb_offset=tb_offset) self._showtraceback(etype, value, stb) shell.set_custom_exc((NameError,), _handler) def init_ipython_session(shell=None, argv=[], auto_symbols=False, auto_int_to_Integer=False): """Construct new IPython session. """ import IPython if version_tuple(IPython.__version__) >= version_tuple('0.11'): if not shell: # use an app to parse the command line, and init config # 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 import ipapp else: from IPython.frontend.terminal import ipapp app = ipapp.TerminalIPythonApp() # don't draw IPython banner during initialization: app.display_banner = False app.initialize(argv) shell = app.shell if auto_symbols: enable_automatic_symbols(shell) if auto_int_to_Integer: enable_automatic_int_sympification(shell) return shell else: from IPython.Shell import make_IPython return make_IPython(argv) def init_python_session(): """Construct new Python session. """ from code import InteractiveConsole class SymPyConsole(InteractiveConsole): """An interactive console with readline support. """ def __init__(self): ns_locals = dict() InteractiveConsole.__init__(self, locals=ns_locals) try: import rlcompleter import readline except ImportError: pass else: import os import atexit readline.set_completer(rlcompleter.Completer(ns_locals).complete) readline.parse_and_bind('tab: complete') if hasattr(readline, 'read_history_file'): history = os.path.expanduser('~/.sympy-history') try: readline.read_history_file(history) except OSError: pass atexit.register(readline.write_history_file, history) return SymPyConsole() def init_session(ipython=None, pretty_print=True, order=None, use_unicode=None, use_latex=None, quiet=False, auto_symbols=False, auto_int_to_Integer=False, str_printer=None, pretty_printer=None, latex_printer=None, argv=[]): """ Initialize an embedded IPython or Python session. The IPython session is initiated with the --pylab option, without the numpy imports, so that matplotlib plotting can be interactive. Parameters ========== pretty_print: boolean If True, use pretty_print to stringify; if False, use sstrrepr to stringify. order: string or None 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: boolean or None If True, use unicode characters; if False, do not use unicode characters. use_latex: boolean or None If True, use latex rendering if IPython GUI's; if False, do not use latex rendering. quiet: boolean If True, init_session will not print messages regarding its status; if False, init_session will print messages regarding its status. auto_symbols: boolean If True, IPython will automatically create symbols for you. If False, it will not. The default is False. auto_int_to_Integer: boolean If True, IPython will automatically wrap int literals with Integer, so that things like 1/2 give Rational(1, 2). If False, it will not. The default is False. ipython: boolean or None If True, printing will initialize for an IPython console; if False, printing will initialize for a normal console; The default is None, which automatically determines whether we are in an ipython instance or not. str_printer: function, optional, default=None A custom string printer function. This should mimic sympy.printing.sstrrepr(). pretty_printer: function, optional, default=None A custom pretty printer. This should mimic sympy.printing.pretty(). latex_printer: function, optional, default=None A custom LaTeX printer. This should mimic sympy.printing.latex() This should mimic sympy.printing.latex(). argv: list of arguments for IPython See sympy.bin.isympy for options that can be used to initialize IPython. See Also ======== sympy.interactive.printing.init_printing: for examples and the rest of the parameters. Examples ======== >>> from sympy import init_session, Symbol, sin, sqrt >>> sin(x) #doctest: +SKIP NameError: name 'x' is not defined >>> init_session() #doctest: +SKIP >>> sin(x) #doctest: +SKIP sin(x) >>> sqrt(5) #doctest: +SKIP ___ \\/ 5 >>> init_session(pretty_print=False) #doctest: +SKIP >>> sqrt(5) #doctest: +SKIP sqrt(5) >>> y + x + y**2 + x**2 #doctest: +SKIP x**2 + x + y**2 + y >>> init_session(order='grlex') #doctest: +SKIP >>> y + x + y**2 + x**2 #doctest: +SKIP x**2 + y**2 + x + y >>> init_session(order='grevlex') #doctest: +SKIP >>> y * x**2 + x * y**2 #doctest: +SKIP x**2*y + x*y**2 >>> init_session(order='old') #doctest: +SKIP >>> x**2 + y**2 + x + y #doctest: +SKIP x + y + x**2 + y**2 >>> theta = Symbol('theta') #doctest: +SKIP >>> theta #doctest: +SKIP theta >>> init_session(use_unicode=True) #doctest: +SKIP >>> theta # doctest: +SKIP \u03b8 """ import sys in_ipython = False if ipython is not False: try: import IPython except ImportError: if ipython is True: raise RuntimeError("IPython is not available on this system") ip = None else: try: from IPython import get_ipython ip = get_ipython() except ImportError: ip = None in_ipython = bool(ip) if ipython is None: ipython = in_ipython if ipython is False: ip = init_python_session() mainloop = ip.interact else: ip = init_ipython_session(ip, argv=argv, auto_symbols=auto_symbols, auto_int_to_Integer=auto_int_to_Integer) if version_tuple(IPython.__version__) >= version_tuple('0.11'): # runsource is gone, use run_cell instead, which doesn't # take a symbol arg. The second arg is `store_history`, # and False means don't add the line to IPython's history. ip.runsource = lambda src, symbol='exec': ip.run_cell(src, False) # Enable interactive plotting using pylab. try: ip.enable_pylab(import_all=False) except Exception: # Causes an import error if matplotlib is not installed. # Causes other errors (depending on the backend) if there # is no display, or if there is some problem in the # backend, so we have a bare "except Exception" here pass if not in_ipython: mainloop = ip.mainloop if auto_symbols and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')): raise RuntimeError("automatic construction of symbols is possible only in IPython 0.11 or above") if auto_int_to_Integer and (not ipython or version_tuple(IPython.__version__) < version_tuple('0.11')): raise RuntimeError("automatic int to Integer transformation is possible only in IPython 0.11 or above") _preexec_source = preexec_source ip.runsource(_preexec_source, symbol='exec') init_printing(pretty_print=pretty_print, order=order, use_unicode=use_unicode, use_latex=use_latex, ip=ip, str_printer=str_printer, pretty_printer=pretty_printer, latex_printer=latex_printer) message = _make_message(ipython, quiet, _preexec_source) if not in_ipython: print(message) mainloop() sys.exit('Exiting ...') else: print(message) import atexit atexit.register(lambda: print("Exiting ...\n"))