Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 374
WilliamKF Avatar asked Jan 04 '10 18:01

WilliamKF


People also ask

How do you create a dynamic variable in Python?

Use the for Loop to Create a Dynamic Variable Name in Python The globals() method in Python provides the output as a dictionary of the current global symbol table. The following code uses the for loop and the globals() method to create a dynamic variable name in Python.

Does Python have dynamic scoping?

Python uses lexical scoping, there is no dynamic scoping.

How do you pass a variable dynamically in Python?

You can create a dict using the variable and then unpack while passing it to the function: list_of_variable_names = ['myMsg','anotherMessages'] for name in list_of_variable_names: helloWorld(**{name: '...'})


2 Answers

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)

like image 81
jsbueno Avatar answered Sep 20 '22 14:09

jsbueno


Here's something that works a bit like Lisp's special variables, but fits a little better into Python.

_stack = []

class _EnvBlock(object):
    def __init__(self, kwargs):
        self.kwargs = kwargs
    def __enter__(self):
        _stack.append(self.kwargs)
    def __exit__(self, t, v, tb):
        _stack.pop()

class _Env(object):
    def __getattr__(self, name):
        for scope in reversed(_stack):
            if name in scope:
                return scope[name]
        raise AttributeError("no such variable in environment")
    def let(self, **kwargs):
        return _EnvBlock(kwargs)
    def __setattr__(self, name, value):
        raise AttributeError("env variables can only be set using `with env.let()`")

env = _Env()

You can use it like this:

with env.let(bufsize=8192, encoding="ascii"):
    print env.bufsize  # prints 8192
    a()  # call a function that uses env.bufsize or env.encoding

The effects of env.let last for the duration of the with block.

Note that if you use threads, you'll definitely want a different _stack for each thread. You could use threading.local to implement that.

like image 21
Jason Orendorff Avatar answered Sep 19 '22 14:09

Jason Orendorff