55# Inspired by similar code by Jeff Epler and Fredrik Lundh.
66
77
8+ import builtins
89import sys
910import traceback
1011from codeop import CommandCompiler , compile_command
@@ -169,7 +170,7 @@ class InteractiveConsole(InteractiveInterpreter):
169170
170171 """
171172
172- def __init__ (self , locals = None , filename = "<console>" ):
173+ def __init__ (self , locals = None , filename = "<console>" , local_exit = False ):
173174 """Constructor.
174175
175176 The optional locals argument will be passed to the
@@ -181,6 +182,7 @@ def __init__(self, locals=None, filename="<console>"):
181182 """
182183 InteractiveInterpreter .__init__ (self , locals )
183184 self .filename = filename
185+ self .local_exit = local_exit
184186 self .resetbuffer ()
185187
186188 def resetbuffer (self ):
@@ -219,27 +221,64 @@ def interact(self, banner=None, exitmsg=None):
219221 elif banner :
220222 self .write ("%s\n " % str (banner ))
221223 more = 0
222- while 1 :
223- try :
224- if more :
225- prompt = sys .ps2
226- else :
227- prompt = sys .ps1
224+
225+ # When the user uses exit() or quit() in their interactive shell
226+ # they probably just want to exit the created shell, not the whole
227+ # process. exit and quit in builtins closes sys.stdin which makes
228+ # it super difficult to restore
229+ #
230+ # When self.local_exit is True, we overwrite the builtins so
231+ # exit() and quit() only raises SystemExit and we can catch that
232+ # to only exit the interactive shell
233+
234+ _exit = None
235+ _quit = None
236+
237+ if self .local_exit :
238+ if hasattr (builtins , "exit" ):
239+ _exit = builtins .exit
240+ builtins .exit = Quitter ("exit" )
241+
242+ if hasattr (builtins , "quit" ):
243+ _quit = builtins .quit
244+ builtins .quit = Quitter ("quit" )
245+
246+ try :
247+ while True :
228248 try :
229- line = self .raw_input (prompt )
230- except EOFError :
231- self .write ("\n " )
232- break
233- else :
234- more = self .push (line )
235- except KeyboardInterrupt :
236- self .write ("\n KeyboardInterrupt\n " )
237- self .resetbuffer ()
238- more = 0
239- if exitmsg is None :
240- self .write ('now exiting %s...\n ' % self .__class__ .__name__ )
241- elif exitmsg != '' :
242- self .write ('%s\n ' % exitmsg )
249+ if more :
250+ prompt = sys .ps2
251+ else :
252+ prompt = sys .ps1
253+ try :
254+ line = self .raw_input (prompt )
255+ except EOFError :
256+ self .write ("\n " )
257+ break
258+ else :
259+ more = self .push (line )
260+ except KeyboardInterrupt :
261+ self .write ("\n KeyboardInterrupt\n " )
262+ self .resetbuffer ()
263+ more = 0
264+ except SystemExit as e :
265+ if self .local_exit :
266+ self .write ("\n " )
267+ break
268+ else :
269+ raise e
270+ finally :
271+ # restore exit and quit in builtins if they were modified
272+ if _exit is not None :
273+ builtins .exit = _exit
274+
275+ if _quit is not None :
276+ builtins .quit = _quit
277+
278+ if exitmsg is None :
279+ self .write ('now exiting %s...\n ' % self .__class__ .__name__ )
280+ elif exitmsg != '' :
281+ self .write ('%s\n ' % exitmsg )
243282
244283 def push (self , line ):
245284 """Push a line to the interpreter.
@@ -276,8 +315,22 @@ def raw_input(self, prompt=""):
276315 return input (prompt )
277316
278317
318+ class Quitter :
319+ def __init__ (self , name ):
320+ self .name = name
321+ if sys .platform == "win32" :
322+ self .eof = 'Ctrl-Z plus Return'
323+ else :
324+ self .eof = 'Ctrl-D (i.e. EOF)'
325+
326+ def __repr__ (self ):
327+ return f'Use { self .name } or { self .eof } to exit'
328+
329+ def __call__ (self , code = None ):
330+ raise SystemExit (code )
331+
279332
280- def interact (banner = None , readfunc = None , local = None , exitmsg = None ):
333+ def interact (banner = None , readfunc = None , local = None , exitmsg = None , local_exit = False ):
281334 """Closely emulate the interactive Python interpreter.
282335
283336 This is a backwards compatible interface to the InteractiveConsole
@@ -290,9 +343,10 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None):
290343 readfunc -- if not None, replaces InteractiveConsole.raw_input()
291344 local -- passed to InteractiveInterpreter.__init__()
292345 exitmsg -- passed to InteractiveConsole.interact()
346+ local_exit -- passed to InteractiveConsole.__init__()
293347
294348 """
295- console = InteractiveConsole (local )
349+ console = InteractiveConsole (local , local_exit = local_exit )
296350 if readfunc is not None :
297351 console .raw_input = readfunc
298352 else :
0 commit comments