Nikola Benes
Nikola Benes

Reputation: 2711

Stop execution of a python script as if it ran to the end

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:

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:

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

  2. 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.

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

  4. 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.

  5. 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

Answers (0)

Related Questions