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

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

2 

3from sympy.external.gmpy import GROUND_TYPES 

4from sympy.external.importtools import version_tuple 

5 

6from sympy.interactive.printing import init_printing 

7 

8from sympy.utilities.misc import ARCH 

9 

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""" 

17 

18verbose_message = """\ 

19These commands were executed: 

20%(source)s 

21Documentation can be found at https://docs.sympy.org/%(version)s 

22""" 

23 

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""" 

29 

30 

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 

35 

36 import sys 

37 import os 

38 

39 if quiet: 

40 return "" 

41 

42 python_version = "%d.%d.%d" % sys.version_info[:3] 

43 

44 if ipython: 

45 shell_name = "IPython" 

46 else: 

47 shell_name = "Python" 

48 

49 info = ['ground types: %s' % GROUND_TYPES] 

50 

51 cache = os.getenv('SYMPY_USE_CACHE') 

52 

53 if cache is not None and cache.lower() == 'no': 

54 info.append('cache: off') 

55 

56 if SYMPY_DEBUG: 

57 info.append('debugging: on') 

58 

59 args = shell_name, sympy_version, python_version, ARCH, ', '.join(info) 

60 message = "%s console for SymPy %s (Python %s-%s) (%s)\n" % args 

61 

62 if source is None: 

63 source = preexec_source 

64 

65 _source = "" 

66 

67 for line in source.split('\n')[:-1]: 

68 if not line: 

69 _source += '\n' 

70 else: 

71 _source += '>>> ' + line + '\n' 

72 

73 doc_version = sympy_version 

74 if 'dev' in doc_version: 

75 doc_version = "dev" 

76 else: 

77 doc_version = "%s/" % doc_version 

78 

79 message += '\n' + verbose_message % {'source': _source, 

80 'version': doc_version} 

81 

82 return message 

83 

84 

85def int_to_Integer(s): 

86 """ 

87 Wrap integer literals with Integer. 

88 

89 This is based on the decistmt example from 

90 https://docs.python.org/3/library/tokenize.html. 

91 

92 Only integer literals are converted. Float literals are left alone. 

93 

94 Examples 

95 ======== 

96 

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 

112 

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 

121 

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) 

135 

136 

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 

143 

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) 

157 

158 shell.run_cell = my_run_cell 

159 

160 

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. 

199 

200 import re 

201 re_nameerror = re.compile( 

202 "name '(?P<symbol>[A-Za-z_][A-Za-z0-9_]*)' is not defined") 

203 

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)) 

208 

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) 

213 

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) 

224 

225 stb = self.InteractiveTB.structured_traceback( 

226 etype, value, tb, tb_offset=tb_offset) 

227 self._showtraceback(etype, value, stb) 

228 

229 shell.set_custom_exc((NameError,), _handler) 

230 

231 

232def init_ipython_session(shell=None, argv=[], auto_symbols=False, auto_int_to_Integer=False): 

233 """Construct new IPython session. """ 

234 import IPython 

235 

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() 

247 

248 # don't draw IPython banner during initialization: 

249 app.display_banner = False 

250 app.initialize(argv) 

251 

252 shell = app.shell 

253 

254 if auto_symbols: 

255 enable_automatic_symbols(shell) 

256 if auto_int_to_Integer: 

257 enable_automatic_int_sympification(shell) 

258 

259 return shell 

260 else: 

261 from IPython.Shell import make_IPython 

262 return make_IPython(argv) 

263 

264 

265def init_python_session(): 

266 """Construct new Python session. """ 

267 from code import InteractiveConsole 

268 

269 class SymPyConsole(InteractiveConsole): 

270 """An interactive console with readline support. """ 

271 

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 

283 

284 readline.set_completer(rlcompleter.Completer(ns_locals).complete) 

285 readline.parse_and_bind('tab: complete') 

286 

287 if hasattr(readline, 'read_history_file'): 

288 history = os.path.expanduser('~/.sympy-history') 

289 

290 try: 

291 readline.read_history_file(history) 

292 except OSError: 

293 pass 

294 

295 atexit.register(readline.write_history_file, history) 

296 

297 return SymPyConsole() 

298 

299 

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. 

308 

309 Parameters 

310 ========== 

311 

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. 

355 

356 See Also 

357 ======== 

358 

359 sympy.interactive.printing.init_printing: for examples and the rest of the parameters. 

360 

361 

362 Examples 

363 ======== 

364 

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 

396 

397 in_ipython = False 

398 

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 

415 

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) 

422 

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) 

428 

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 

440 

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") 

445 

446 _preexec_source = preexec_source 

447 

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) 

453 

454 message = _make_message(ipython, quiet, _preexec_source) 

455 

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"))