Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create a dynamic localized scope in Python?

Tags:

python

scope

I have a scenario where I'm dynamically running functions at run-time and need to keep track of a "localized" scope. In the example below, "startScope" and "endScope" would actually be creating levels of "nesting" (in reality, the stuff contained in this localized scope isn't print statements...it's function calls that send data elsewhere and the nesting is tracked there. startScope / endScope just set control flags that are used to start / end the current nesting depth).

This all works fine for tracking the nested data, however, exceptions are another matter. Ideally, an exception would result in "falling out" of the current localized scope and not end the entire function (myFunction in the example below).

def startScope():
    #Increment our control object's (not included in this example) nesting depth
    control.incrementNestingDepth()

def endScope():
    #Decrement our control object's (not included in this example) nesting depth
    control.decrementNestingDepth()

def myFunction():
    print "A"
    print "B"

    startScope()
    print "C"
    raise Exception
    print "D"
    print "This print statement and the previous one won't get printed"
    endScope()

    print "E"

def main():
    try:
        myFunction()
    except:
        print "Error!"

Running this would (theoretically) output the following:

>>> main()
A
B
C
Error!
E

>>>

I'm quite certain this isn't possible as I've written it above - I just wanted to paint a picture of the sort of end-result I'm trying to achieve.

Is something like this possible in Python?

Edit: A more relevant (albeit lengthy) example of how this is actually being used:

class Log(object):
    """
    Log class
    """

    def __init__(self):
        #DataModel is defined elsewhere and contains a bunch of data structures / handles nested data / etc...
        self.model = DataModel()

    def Warning(self, text):
        self.model.put("warning", text)

    def ToDo(self, text):
        self.model.put("todo", text)

    def Info(self, text):
        self.model.put("info", text)

    def StartAdvanced(self):
        self.model.put("startadvanced")

    def EndAdvanced(self):
        self.model.put("endadvanced")

    def AddDataPoint(self, data):
        self.model.put("data", data)

    def StartTest(self):
        self.model.put("starttest")

    def EndTest(self):
        self.model.put("endtest")

    def Error(self, text):
        self.model.put("error", text)


#myScript.py

from Logger import Log

def test_alpha():
    """
    Crazy contrived example

    In this example, there are 2 levels of nesting...everything up to StartAdvanced(),
    and after EndAdvanced() is included in the top level...everything between the two is
    contained in a separate level.
    """

    Log.Warning("Better be careful here!")
    Log.AddDataPoint(fancyMath()[0])

    data = getSerialData()

    if data:
        Log.Info("Got data, let's continue with an advanced test...")

        Log.StartAdvanced()

        #NOTE: If something breaks in one of the following methods, then GOTO (***)
        operateOnData(data)
        doSomethingCrazy(data)
        Log.ToDo("Fill in some more stuff here later...")
        Log.AddDataPoint(data)

        Log.EndAdvanced()

    #(***) Ideally, we would resume here if an exception is raised in the above localized scope
    Log.Info("All done!  Log some data and wrap everything up!")
    Log.AddDataPoint({"data": "blah"})

    #Done


#framework.py

import inspect
from Logger import Log

class Framework(object):

    def __init__(self):
        print "Framework init!"
        self.tests = []

    def loadTests(self, file):
        """
        Simplifying this for the sake of clarity
        """

        for test in file:
            self.tests.append(test)

    def runTests(self):
        """
        Simplifying this for the sake of clarity
        """

        #test_alpha() as well as any other user tests will be run here
        for test in self.tests:
            Log.StartTest()

            try:
                test()
            except Exception,e :
                Log.Error(str(e))

            Log.EndTest()

#End
like image 500
Novark Avatar asked Oct 20 '22 01:10

Novark


1 Answers

You can achieve a similar effect with a context manager using a with statement. Here I use the contextlib.contextmanager decorator:

@contextlib.contextmanager
def swallower():
    try:
        yield
    except ZeroDivisionError:
        print("We stopped zero division error")

def foo():
    print("This error will be trapped")
    with swallower():
        print("Here comes error")
        1/0
        print("This will never be reached")
    print("Merrily on our way")
    with swallower():
        print("This error will propagate")
        nonexistentName
    print("This won't be reached")

>>> foo()
This error will be trapped
Here comes error
We stopped zero division error
Merrily on our way
This error will propagate
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    foo()
  File "<pyshell#3>", line 10, in foo
    nonexistentName
NameError: global name 'nonexistentName' is not defined

It cannot be done with an ordinary function call as in your example. In your example, the function startScope returns before the rest of the body of myFunction executes, so startScope can't have any effect on it. To handle exceptions, you need some kind of explicit structure (either a with statement or a regular try/except) inside myFunction; there's no way to make a simple function call magically intercept exceptions that are raised in its caller.

You should read up on context managers as they seem to fit what you're trying to do. The __enter__ and __exit__ methods of the context manager would correspond to your startScope and endScope. Whether it will do exactly what you want depends on exactly what you want those "manager" functions to do, but you will probably have more luck doing it with a context manager than trying to do it with simple function calls.

like image 67
BrenBarn Avatar answered Nov 02 '22 04:11

BrenBarn