How To Create Dynamical Scoped Variables In Python?
Solution 1:
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
classDynamicVars(object):
def__init__(self):
object.__setattr__(self, "variables", {})
defnormalize(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}
ifnot 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
eliflen(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):
ifnot 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:defc():
D = DynamicVars()
D.c = "old"print D.c
defa():
print D.c
a()
defb():
D.c = "new"
a()
b()
a()
defc():
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)
Solution 2:
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 inscope:return scope[name]
raise AttributeError("no such variable in environment")
deflet(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.
Solution 3:
The Python idiom corresponding to Lisp "special" or dynamically-scoped variables is "thread local storage".
Here is a good discussion: What is "thread local storage" in Python, and why do I need it?
If you want to fully emulate Lisp's special variables, including the let statement, you can use a context manager:
from __future__ import with_statement # if Python 2.5from contextlib import contextmanager
import threading
dyn = threading.local()
@contextmanagerdefdyn_vars(**new):
old = {}
for name, value in new.items():
old[name] = getattr(dyn, name, None)
setattr(dyn, name, value)
yieldfor name, value in old.items():
setattr(dyn, name, value)
Example (patently silly, but it shows the reentrant feature):
def greet_self():
print 'Hi', dyn.who_am_I
def greet_selves():
withdyn_vars(who_am_I='Evil Twin'):
greet_self()
greet_self()
withdyn_vars(who_am_I='Tobia'):
greet_selves()
Solution 4:
Dynamic Scoping Considered Harmful.
Don't use it; don't emulate it.
If you need to emulate it, define a dynamic_scope
module to emulate this behavior and import the module in all source files. This module should have methods begin
which is called in the first line of your functions that use dynamic scopes, end
, get
, and set
. The get
and set
methods should implement looking up the call chain for variable names where the call chain is implemented by begin
and end
. Then refactor your code to eliminate dynamic scopes.
Post a Comment for "How To Create Dynamical Scoped Variables In Python?"