Reputation: 2711
I want to stop the execution of a Python (3.12) script with a behaviour that is identical to the script running to completion in ideally all contexts, such as:
python script.py
;python -i script.py
;To simplify the discussion, let us assume a simple Python script as follows: (EDIT: This is just a simple example to play with; in reality, the exit function could be called from somewhere nested in a branch, a loop, or whatever.)
x = 1
print("HELLO")
magic_exit_function()
print("won't see this")
y = 1
I want a magic_exit_function
that (a) ensures that HELLO
is output, but won't see this
isn't, (b) if run in interactive mode, ensures that the value of x
can be inspected after the script ends, but not the value of y
, and (c) doesn't produce any noise on stdout or stderr.
I have tried the following:
Use exit
or quit
or sys.exit
. This works with python
; with python -i
it almost works (there is an ugly traceback with SystemExit, not satisfying (c)); with Thonny's shell it ends the subprocess and the value of x
cannot be inspected afterwards (thus not satisfying (b)).
Raise a new kind of exception deriving from Exception
. This produces noise, but at least I can inspect the value of x
even in Thonny's shell.
Raise a new kind of exception deriving from Exception
and set the sys.excepthook
to ignore this kind of exception specifically. Works well with python
and python -i
, but still produces noise in Thonny's shell (Thonny seems to catch the exception and ignore sys.excepthook
).
Try the same thing, but derive the exception from BaseException
. There is no noise in Thonny, but the subprocess ends, and I cannot inspect the value of x
afterwards.
Use sys.settrace
and the ability to set f_lineno
of the current frame. This almost works (in all tested scenarios), but the problem is that I cannot use this to jump to the end of the current module; I can only jump to the last line of the module, and this last line is always executed. This breaks (b), because the y = 1
line is executed.
Are there any other possibilities without changing the script itself (apart from the magic_exit_function
)?
The implementation of the various attempts at magic_exit_function
follow:
def magic_exit_function1():
import sys
sys.exit() # or exit() or quit() or raise SystemExit()
def magic_exit_function2():
class MyExit(Exception):
pass
raise MyExit()
def magic_exit_function3():
import sys
class MyExit(Exception):
pass
def hook(exc_type, exc, tb):
if exc_type is MyExit:
return None
return sys.__excepthook__(exc_type, exc, tb)
sys.excepthook = hook
raise MyExit()
def magic_exit_function4():
import sys
class MyExit(BaseException):
pass
def hook(exc_type, exc, tb):
if exc_type is MyExit:
return None
return sys.__excepthook__(exc_type, exc, tb)
sys.excepthook = hook
raise MyExit()
def magic_exit_function5():
import sys
import inspect
def trace(frame, event, arg):
if event != 'line':
return trace
frame.f_lineno = last_line
return trace
sys.settrace(lambda *args, **kwargs: None)
frame = inspect.currentframe().f_back
_, _, last_line = list(frame.f_code.co_lines())[-1]
frame.f_trace = trace
Upvotes: 2
Views: 96