Exec() with 'return' keyword

Show me where the author wanted to skip any code. I can’t find it. Everything about “resume” is “resume from the same point”, or possibly “resume from a bit earlier” (if snapshots are taken periodically). Never “resume from a bit later”.

If it is not helpful, then skip it. I just view random documents to spot words and enter them into search engines.

That discussion does not offer any solution.

About the understanding of ‘resume’, you can start anywhere as long as it will fix the bug. Nothing much to said about skipping. Skipping means to spot and stop running the buggy code, and continue to run the bug-free code in my opinion.

The discussion above may differ from my solution, but my solution has the same purpose of that discussion.

As you saw from what led to this post in the first place, this statement is simply not true.

Well it could be done, maybe starting anywhere will not fix the problem. When doing live-patching, it is common to remove certain lines of buggy code and insert bug-free code on-the-fly, and it is possible to go back to certain line and execute the following code. In general it involves skipping, deleting, going back and insertion. In the process you know where your code is at, and what code will be run next. It’s like a Turing machine with human intervention. The states may or may not be restored, depending on the situation and implementation. Even if without previous state restored, one can fix the situation with handwritten code by looking into the current state. If you think certain following lines will cause trouble you can remove or skip them, without the need for machine to run and raise exceptions for you.

I’m not saying this is for everyone. This is just how I would use it to debug my programs. Python is a scripting language, which makes it easier to do such things.

Live patching can also be persistent, modifying the running code and the code on disk at the same time, commenting out the buggy code.

There seems to be some solution. I somehow figured out.

def error_func():
    try:
        return a
    except:
        print('you must somehow return something')
        while True:
            code = input('please set some return statement.\n')
            if code.startswith('return '):
                myReturnValue = eval("{}".format(code.replace('return ','')))
                print('about to return value')
                return myReturnValue
            else:
                exec(code)

val = error_func()
print('value returned:',val)

I thought reversible debuggers were only practical in languages with a significant amount of side-effectless computation, like Haskell?

Python is not reversible by design, but with human intervention it can be reversible at certain degree.

Looks like python opcodes are promising.

There’s such concept called “post-mortem debugging”. I’d like to call mine “ante-mortem debugging” or “resurrection”. Though the naming is not good since I’d searched through the internet and found nothing relevant to these ‘invented concepts’, it’s a good place to start and people will have a better grasp of my technique.

Out of curiosity, what kind of environment are you working in where you need these kinds of techniques?

Some code that will run for 4 hours or something and suddenly pop up a bug.

Posted a new issue for cpython.

Can you be more specific? I work on web apps in an organisation where bugs aren’t costing huge amounts of money to be lost every minute or something. So, even if we need to fix something urgently, we’ll just do it in our normal way: letting the CD system take care of building and deployment after the changes have been pushed.

I’m assuming you work under a different set of constraints, so it’d be interesting to hear about those.

Deeplearning, rendering, or anything does not fully support hot-reloading at some extent, yet requires tons of time to run and debug. I managed to find some good libraries like reloading though.

My modified version of reloading: repo

It’s possible to transform your code into ‘immortal’ with tools like comby, semgrep, pasta, redbaron

Posted issues for debugpy and reloadium.

Example usage:

from reloading import reloading
import asyncio


@reloading
class mClass:
    someValue = 2

    @reloading
    def someMethod(self):
        @reloading
        def someInnerMethod():
            raise Exception("inner exception")
            return "inside function return"
        raise Exception("exception")
        val = someInnerMethod()
        return val

    @reloading
    async def someOtherMethod(self):
        @reloading
        async def asyncInside():
            raise Exception("inner async exception")
            return "async inside return"
        raise Exception('async exception')
        val = await asyncInside()
        return val

    @reloading
    def runAsync(self):
        loop = asyncio.get_event_loop()
        val = loop.run_until_complete(self.someOtherMethod())
        print("value from async func:", val)
        return val


@reloading
def main():
    MClass = mClass
    print(MClass)
    val = MClass.someMethod()
    print("return value:", val)
    val = MClass.runAsync()
    print("return async value:", val)
    print("success!")


main()

There is a much MUCH safer way to do reloading. Instead of fiddling with functions inside modules, just reload the entire module at once. That requires no messing around with exec hackery, no fiddling with namespaces; just exec the entire module in its own new namespace and then hook it into whatever places it needs to be hooked into.

I don’t want to ever have to work with a nightmare like this when there are simple and safe options that already exist.

Common lisp has the capabilities. SBCL even includes capabilities for doing return in REPL.

The core difference is that common lisp uses “resumption semantic” for exception/condition handling while other languages like python adopt “termination semantic”.

Here’s the glimpse:


The debug prompt is square brackets, with number(s) indicating the current
  control stack level and, if you've entered the debugger recursively, how
  deeply recursed you are.
Any command -- including the name of a restart -- may be uniquely abbreviated.
The debugger rebinds various special variables for controlling i/o, sometimes
  to defaults (much like WITH-STANDARD-IO-SYNTAX does) and sometimes to
  its own special values, based on SB-EXT:*DEBUG-PRINT-VARIABLE-ALIST*.
Debug commands do not affect *, //, and similar variables, but evaluation in
  the debug loop does affect these variables.
SB-DEBUG:*FLUSH-DEBUG-ERRORS* controls whether errors at the debug prompt
  drop you deeper into the debugger. The default NIL allows recursive entry
  to debugger.

Getting in and out of the debugger:
  TOPLEVEL, TOP  exits debugger and returns to top level REPL
  RESTART        invokes restart numbered as shown (prompt if not given).
  ERROR          prints the error condition and restart cases.

  The number of any restart, or its name, or a unique abbreviation for its
   name, is a valid command, and is the same as using RESTART to invoke
   that restart.

Changing frames:
  UP     up frame         DOWN     down frame
  BOTTOM bottom frame     FRAME n  frame n (n=0 for top frame)

Inspecting frames:
  BACKTRACE [n]  shows n frames going down the stack.
  LIST-LOCALS, L lists locals in current frame.
  PRINT, P       displays function call for current frame.
  SOURCE [n]     displays frame's source form with n levels of enclosing forms.

Stepping:
  START Selects the CONTINUE restart if one exists and starts
        single-stepping. Single stepping affects only code compiled with
        under high DEBUG optimization quality. See User Manual for details.
  STEP  Steps into the current form.
  NEXT  Steps over the current form.
  OUT   Stops stepping temporarily, but resumes it when the topmost frame that
        was stepped into returns.
  STOP  Stops single-stepping.

Function and macro commands:
 (SB-DEBUG:ARG n)
    Return the n'th argument in the current frame.
 (SB-DEBUG:VAR string-or-symbol [id])
    Returns the value of the specified variable in the current frame.

Other commands:
  RETURN expr
    Return the values resulting from evaluation of expr from the
    current frame, if this frame was compiled with a sufficiently high
    DEBUG optimization quality.

  RESTART-FRAME
    Restart execution of the current frame, if this frame is for a
    global function which was compiled with a sufficiently high
    DEBUG optimization quality.

  SLURP
    Discard all pending input on *STANDARD-INPUT*. (This can be
    useful when the debugger was invoked to handle an error in
    deeply nested input syntax, and now the reader is confused.)

(The HELP string is stored in *DEBUG-HELP-STRING*.)

After some reading, I find SBCL’s debugger is a time-travel debugger at some extent.

CLISP can do that, by invoking with clisp --on-error [debug|appease] [script file].

Those aren’t use-cases. Use-cases are a high-level description in natural language (e.g. English) that describe what and why, not a code example that reads like gobbledegook until the reader knows what the code does.

For example, you have something that calls a decorator three times and appears to be almost entirely dead code that never gets executed:

@reloading
class mClass:
    someValue = 2

    @reloading
    def someMethod(self):
        @reloading
        def someInnerMethod():
            raise Exception("inner exception")
            return "inside function return"
        raise Exception("exception")
        val = someInnerMethod()
        return val

Reading that, it looks like mClass().someMethod() just raises Exception("exception") and stops. I guess that something different happens, because of the magical reloading decorator, but I cannot guess what.

To understand that example, we need to know:

  • what it does, assuming it is something non-standard;
  • what is the expected output of this code;
  • and most importantly, why we want it to do that thing (what’s the use-case?).

Then we come to implementation questions like what performance hit will we take for this feature?

This code has multiple exceptions about to be raised. When you execute the code, because of the reloading decorator, the program won’t exit. Instead, it pauses, waiting for your modification on the original program and reload exactly the definition of the function which raises the exception and re-execute the function. Because there are multiple exceptions about to be raised, you can either fix them at once, or fix them one by one without actually exiting the program.

Try for yourself if interested, or I can post some asciinema recording for demonstration.

The performance hit can be reduced, since I modified the reloading library and added an option to only reload function on exception. Reloading on change is another option, setting up a filesystem watcher and only load/reload part of code that appears to be changed.

The reason to do this is obvious to me, but not obvious to everyone. I’ve explained it here and many explanations floating around the internet to demonstrate the benefit of hot-fixing or whatever you name it, all with respect to the common lisp’s way of handling exceptions as conditions.

What you HAVEN’T shown is why hotfixing in this insanely overengineered way is better than simply reloading an entire module, which can already be done in Python as it currently exists.

So no, it is absolutely NOT obvious to everyone.

If this is what you are talking about, here’s my attempt:

# inside some_module.py

def program():
    raise Exception("Exception in program")
    # return "VALUE"

if __name__ == "__main__":
    while True:
        try:
            import some_module
            val = some_module.program()
            print("returned value:", val)
            break
        except:
            import traceback
            traceback.print_exc()
            input('are you done yet?')
            import importlib
            importlib.reload(some_module)

I doubt this if it still works on closure and existing class instances, but common lisp does enforce this behavior when reloading modules and functions from file. My modified version of ‘reloading’ can do that as well, so it doesn’t hurt to combine them.

James said:

“Some code that will run for 4 hours or something and suddenly pop up a bug.”

Okay, this is the first time I have felt I understand what you are getting at. I’ve been there:

  • you have a function which runs through a huge amount of data;
  • it runs for a long time, say, many hours;
  • you can’t stop in the middle of processing and restart later;
  • and you hit a bug after (say) five hours, when there’s still an hour to go;
  • or worse, just before the function can complete and return your answer.

This sort of batch processing is awful when it fails. It would be nice if instead of an exception, it popped up a debugger, I could see what the error is, fix it and continue.

Jack Rusher gave an interesting talk on this at Strange Loop 2022, I recommend that anyone following this thread watch it.

I found myself agreeing with him about 50% of the time and vehemently disagreeing the other 50% (especially about his mocking 80 column code widths, which have little or nothing to do with teletypes except maybe as an accident of history).

  • Long compile-run-break-debug-compile cycles are awful.
  • But even fast compilers or interpreters often have to deal with long data initialisation and processing times.
  • For many kinds of computing we do, we need live and interactive coding.
  • Spreadsheets work like that, and they’re great.
  • GUI apps should work like that.
  • Data scientists need to work like that, which is why they love Jupyter Notebooks and R.
1 Like