11

I would like to handle a NameError exception by injecting the desired missing variable into the frame and then continue the execution from last attempted instruction.

The following pseudo-code should illustrate my needs.

def function():
    return missing_var

try:
    print function()

except NameError:

    frame = inspect.trace()[-1][0]

    # inject missing variable
    frame.f_globals["missing_var"] = ...

    # continue frame execution from last attempted instruction
    exec frame.f_code from frame.f_lasti

Read the whole unittest on repl.it

Notes

Background

The code runs in a slave process, which is controlled by a parent. Tasks (functions really) are written in the parent and latter passed to the slave using dill. I expect some tasks (running in the slave process) to try to access variables from outer scopes in the parent and I'd like the slave to request those variables to the parent on the fly.

p.s.: I don't expect this magic to run in a production environment.

Community
  • 1
  • 1
Jules Baratoux
  • 123
  • 1
  • 7
  • 3
    The Python exception-handling model wouldn't let you do that, since the stack has to be unwound to determine who handles the exception, and you can't un-return from any C functions that had to return to unwind the stack. You'd have to handle this at the point the NameError is generated. – user2357112 Dec 22 '15 at 22:23
  • 1
    I don't think what you are trying to do is possible in python because when the stack frame is popped, local state will be lost and there will be no way to recover that. What is the driver for you wanting to do this? – dolan Dec 22 '15 at 22:23
  • Thank you very much for your consideration. @dolan, this code run in a slave process, which is controlled by a parent. Tasks are written in the parent and latter passed to the slave using [dill](https://pypi.python.org/pypi/dill). I expect some tasks (running in the slave process) to try to access outer variables (defined in the parent) and I wanted the slave to request those variables on the fly while needed. – Jules Baratoux Dec 22 '15 at 23:29
  • 2
    @JulesBaratoux: NameErrors are not a good way to go about dealing with that. Why not have the slave process access those values through a lazy mapping that requests values from the parent as needed, or explicitly pass the slave the values it's going to need, or something else that doesn't require the awful, excluded-for-a-reason-in-just-about-every-language technique of resuming after exceptions? – user2357112 Dec 23 '15 at 00:23
  • @user2357112: As explained to ivan_pozdeev, I currently pass those variables as argument to avoid the "awful" magic :D. I'll take a look at lazy mapping. Thank you for the advice! – Jules Baratoux Dec 23 '15 at 00:32

3 Answers3

3

The "resumption" exception handling technique has proven to be problematic, that's why it's missing from C++ and later languages.

Your best bet is to use a while loop to not resume where the exception was thrown but rather repeat from a predetermined place:

while True:
    try:
        do_something()
    except NameError as e:
        handle_error()
    else:
        break
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
  • The while loop was my first choice. As explained in the recently added **background** section, the goal is to provide the ability to request variables in an outer scope from the parent process on the fly. The ability to retrieve variable from an outer scope would have been plus in my opinion but currently, those variables are passed as arguments, avoiding the need of that magic. Thank you for the link by the way, Stroustrup references are always welcome! – Jules Baratoux Dec 23 '15 at 00:20
  • 1
    @JulesBaratoux this asks for hooking into the retrieval process, [just like Andre said](http://stackoverflow.com/a/34426840/648265). Trying to fix things _inside a statement_ and _in the same frame_ when _the language only allows flow control at statement granularity in a frame_ is, by this definition, an absolutely impossible task. You must handle the error lower on the frame stack. – ivan_pozdeev Dec 23 '15 at 03:05
  • @JulesBaratoux ...or split the statement at the point where you require flow control. – ivan_pozdeev May 25 '16 at 07:04
3

You really can't unwind the stack after an exception is thrown, so you'd have to deal with the issue before hand. If your requirement is to generate these variables on the fly (which wouldn't be recommended, but you seem to understand that), then you'd have to actually request them. You can implement a mechanism for that (such as having a global custom Scope class instance and overriding __getitem__, or using something like the __dir__ function), but not as you are asking for it.

André Fratelli
  • 5,920
  • 7
  • 46
  • 87
  • 1
    It sounds like I should avoid the exception case rather than to mysteriously handle it. I like the [`__getitem__`](https://docs.python.org/3/reference/datamodel.html#object.__getitem__) approach. Thank you! – Jules Baratoux Dec 23 '15 at 01:02
3

On the contrary to what various commenters are saying, "resume-on-error" exception handling is possible in Python. The library fuckit.py implements said strategy. It steamrollers errors by rewriting the source code of your module at import time, inserting try...except blocks around every statement and swallowing all exceptions. So perhaps you could try a similar sort of tactic?

It goes without saying: that library is intended as a joke. Don't ever use it in production code.


You mentioned that your use case is to trap references to missing names. Have you thought about using metaprogramming to run your code in the context of a "smart" namespace such as a defaultdict? (This is perhaps only marginally less of a bad idea than fuckit.py.)

from collections import defaultdict

class NoMissingNamesMeta(type):
    @classmethod
    def __prepare__(meta, name, bases):
        return defaultdict(lambda: "foo")

class MyClass(metaclass=NoMissingNamesMeta):
    x = y + "bar"  # y doesn't exist

>>> MyClass.x
'foobar'

NoMissingNamesMeta is a metaclass - a language construct for customising the behaviour of the class statement. Here we're using the __prepare__ method to customise the dictionary which will be used as the class's namespace during creation of the class. Thus, because we're using a defaultdict instead of a regular dictionary, a class whose metaclass is NoMissingNamesMeta will never get a NameError. Any names referred to during the creation of the class will be auto-initialised to "foo".

This approach is similar to @AndréFratelli's idea of manually requesting the lazily-initialised data from a Scope object. In production I'd do that, not this. The metaclass version requires less typing to write the client code, but at the expense of a lot more magic. (Imagine yourself debugging this code in two years, trying to understand why non-existent variables are dynamically being brought into scope!)

Community
  • 1
  • 1
Benjamin Hodgson
  • 42,952
  • 15
  • 108
  • 157
  • Such a library name! Your last suggestion seems to converge with André Fratelli's answer. I'm looking forward to try something related. Thank you! – Jules Baratoux Dec 23 '15 at 00:55
  • 1
    That doesn't actually resume "at the same place where the exception was thrown", but rather at the next statement - at "a predetermined place", like I said. – ivan_pozdeev Dec 23 '15 at 02:48