summaryrefslogtreecommitdiffstats
path: root/Lib/code.py
diff options
context:
space:
mode:
authorTian Gao <gaogaotiantian@hotmail.com>2023-10-18 18:36:43 (GMT)
committerGitHub <noreply@github.com>2023-10-18 18:36:43 (GMT)
commite6eb8cafca441046b5f9ded27c68d9a84c42022a (patch)
treef0bc7f0c17f8e0a2e52b8fd3ba83c78a7d11a606 /Lib/code.py
parentcb1bf89c4066f30c80f7d1193b586a2ff8c40579 (diff)
downloadcpython-e6eb8cafca441046b5f9ded27c68d9a84c42022a.zip
cpython-e6eb8cafca441046b5f9ded27c68d9a84c42022a.tar.gz
cpython-e6eb8cafca441046b5f9ded27c68d9a84c42022a.tar.bz2
GH-102895 Add an option local_exit in code.interact to block exit() from terminating the whole process (GH-102896)
Diffstat (limited to 'Lib/code.py')
-rw-r--r--Lib/code.py100
1 files changed, 77 insertions, 23 deletions
diff --git a/Lib/code.py b/Lib/code.py
index 2bd5fa3..f4aecdd 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -5,6 +5,7 @@
# Inspired by similar code by Jeff Epler and Fredrik Lundh.
+import builtins
import sys
import traceback
from codeop import CommandCompiler, compile_command
@@ -169,7 +170,7 @@ class InteractiveConsole(InteractiveInterpreter):
"""
- def __init__(self, locals=None, filename="<console>"):
+ def __init__(self, locals=None, filename="<console>", local_exit=False):
"""Constructor.
The optional locals argument will be passed to the
@@ -181,6 +182,7 @@ class InteractiveConsole(InteractiveInterpreter):
"""
InteractiveInterpreter.__init__(self, locals)
self.filename = filename
+ self.local_exit = local_exit
self.resetbuffer()
def resetbuffer(self):
@@ -219,27 +221,64 @@ class InteractiveConsole(InteractiveInterpreter):
elif banner:
self.write("%s\n" % str(banner))
more = 0
- while 1:
- try:
- if more:
- prompt = sys.ps2
- else:
- prompt = sys.ps1
+
+ # When the user uses exit() or quit() in their interactive shell
+ # they probably just want to exit the created shell, not the whole
+ # process. exit and quit in builtins closes sys.stdin which makes
+ # it super difficult to restore
+ #
+ # When self.local_exit is True, we overwrite the builtins so
+ # exit() and quit() only raises SystemExit and we can catch that
+ # to only exit the interactive shell
+
+ _exit = None
+ _quit = None
+
+ if self.local_exit:
+ if hasattr(builtins, "exit"):
+ _exit = builtins.exit
+ builtins.exit = Quitter("exit")
+
+ if hasattr(builtins, "quit"):
+ _quit = builtins.quit
+ builtins.quit = Quitter("quit")
+
+ try:
+ while True:
try:
- line = self.raw_input(prompt)
- except EOFError:
- self.write("\n")
- break
- else:
- more = self.push(line)
- except KeyboardInterrupt:
- self.write("\nKeyboardInterrupt\n")
- self.resetbuffer()
- more = 0
- if exitmsg is None:
- self.write('now exiting %s...\n' % self.__class__.__name__)
- elif exitmsg != '':
- self.write('%s\n' % exitmsg)
+ if more:
+ prompt = sys.ps2
+ else:
+ prompt = sys.ps1
+ try:
+ line = self.raw_input(prompt)
+ except EOFError:
+ self.write("\n")
+ break
+ else:
+ more = self.push(line)
+ except KeyboardInterrupt:
+ self.write("\nKeyboardInterrupt\n")
+ self.resetbuffer()
+ more = 0
+ except SystemExit as e:
+ if self.local_exit:
+ self.write("\n")
+ break
+ else:
+ raise e
+ finally:
+ # restore exit and quit in builtins if they were modified
+ if _exit is not None:
+ builtins.exit = _exit
+
+ if _quit is not None:
+ builtins.quit = _quit
+
+ if exitmsg is None:
+ self.write('now exiting %s...\n' % self.__class__.__name__)
+ elif exitmsg != '':
+ self.write('%s\n' % exitmsg)
def push(self, line):
"""Push a line to the interpreter.
@@ -276,8 +315,22 @@ class InteractiveConsole(InteractiveInterpreter):
return input(prompt)
+class Quitter:
+ def __init__(self, name):
+ self.name = name
+ if sys.platform == "win32":
+ self.eof = 'Ctrl-Z plus Return'
+ else:
+ self.eof = 'Ctrl-D (i.e. EOF)'
+
+ def __repr__(self):
+ return f'Use {self.name} or {self.eof} to exit'
+
+ def __call__(self, code=None):
+ raise SystemExit(code)
+
-def interact(banner=None, readfunc=None, local=None, exitmsg=None):
+def interact(banner=None, readfunc=None, local=None, exitmsg=None, local_exit=False):
"""Closely emulate the interactive Python interpreter.
This is a backwards compatible interface to the InteractiveConsole
@@ -290,9 +343,10 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None):
readfunc -- if not None, replaces InteractiveConsole.raw_input()
local -- passed to InteractiveInterpreter.__init__()
exitmsg -- passed to InteractiveConsole.interact()
+ local_exit -- passed to InteractiveConsole.__init__()
"""
- console = InteractiveConsole(local)
+ console = InteractiveConsole(local, local_exit=local_exit)
if readfunc is not None:
console.raw_input = readfunc
else: