-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-102895 Add an option local_exit in code.interact to block exit() from terminating the whole process #102896
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d992ed1
67038e3
68dbfc1
bc5c110
9fa32a1
eedb3e2
3c58bf8
1916273
56d7237
2dc19a4
91befb5
b69feca
818fbac
ada2ce1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 @@ def __init__(self, locals=None, filename="<console>"): | |
""" | ||
InteractiveInterpreter.__init__(self, locals) | ||
self.filename = filename | ||
self.local_exit = local_exit | ||
self.resetbuffer() | ||
|
||
def resetbuffer(self): | ||
|
@@ -219,27 +221,64 @@ def interact(self, banner=None, exitmsg=None): | |
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 @@ def raw_input(self, prompt=""): | |
return input(prompt) | ||
|
||
|
||
class Quitter: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe just add a short docstring explaining that this is based on Perhaps it's even worth just subclassing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Subclassing |
||
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: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Added a parameter ``local_exit`` for :func:`code.interact` to prevent ``exit()`` and ``quit`` from closing ``sys.stdin`` and raise ``SystemExit``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's interesting! I didn't know about this additional wrinkle... thanks for the comment.