pythonvariableslispscopedynamic-scope

How to create dynamical scoped variables in Python?


I am translating some code from lisp to Python.

In lisp, you can have a let construct with the variables introduced declared as special and thus having dynamic scope. (See http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping)

How can I do likewise in Python? It seems the language does not support this directly, if true, what would be a good way to emulate it?


Solution

  • I feel Justice is plain right in his reasoning here.

    On the other hand -- I can't resist implementing proof of concept for still another programing paradigm "unnatural" to Python -- I simply love doing this. :-)

    So, I created a class whose objects'attributes are scopped just like you require (and can be created dynamically). As I said, it is just in a proof of concept state - but I think most usual errors, (like trying to access a variable ina scope it is not defined at all) should have errors raised, even if not the proper ones (IndexError due to a stack underflow instead of AttributeError, for example)

    import inspect
    
    
    class DynamicVars(object):
        def __init__(self):
            object.__setattr__(self, "variables", {})
    
        def normalize(self, stackframe):
            return [hash(tpl[0]) for tpl in stackframe[1:]]
    
        def __setattr__(self, attr, value):
            stack = self.normalize(inspect.stack())
            d = {"value": value, "stack": stack}
            if not attr in self.variables:
                self.variables[attr] = []
                self.variables[attr].append(d)
            else:
                our_value = self.variables[attr]
                if our_value[-1]["stack"] == stack:
                    our_value[-1]["value"] = value
                elif len(stack) <= len(our_value):
                    while our_value and stack !=  our_value["stack"]:
                        our_value.pop()
                    our_value.append(d)
                else: #len(stack) > len(our_value):
                    our_value.append(d)
        def __getattr__(self, attr):
            if not attr in self.variables:
                raise AttributeError
            stack = self.normalize(inspect.stack())
            while self.variables[attr]:
                our_stack = self.variables[attr][-1]["stack"]
                if our_stack == stack[-len(our_stack):]:
                    break
                self.variables[attr].pop()
            else:
                raise AttributeError
            return self.variables[attr][-1]["value"]
    
    
    # for testing:
    def c():
        D = DynamicVars()
        D.c = "old"
        print D.c
        def a():
            print D.c
        a()
        def b():
            D.c = "new"
            a()
        b()
        a()
        def c():
            D.c = "newest"
            a()
            b()
            a()
        c()
        a()
    
    c()
    

    2020 update - Another similar question showed up, and I crafted a hack that needs no special namespace objects (but which resorts to using inner things from cPython, like updating the locals() to actual variables: https://stackoverflow.com/a/61015579/108205 (works with Python 3.8)