我正在将一些代码从lisp转换为Python。如何在Python中创建动态范围变量?
在lisp中,您可以使用let构造,其中声明的变量被声明为特殊的,因此具有动态范围。 (请参见http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping)
我该怎么在Python中做同样的事情?看起来语言不直接支持这一点,如果这是真的,那么模仿它的好方法是什么?
我正在将一些代码从lisp转换为Python。如何在Python中创建动态范围变量?
在lisp中,您可以使用let构造,其中声明的变量被声明为特殊的,因此具有动态范围。 (请参见http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping)
我该怎么在Python中做同样的事情?看起来语言不直接支持这一点,如果这是真的,那么模仿它的好方法是什么?
我觉得正义在他的推理中是正确的。另一方面 - 我无法抗拒对Python的另一种编程范式“非自然”进行概念验证 - 我只是喜欢这样做。 :-)
所以,我创建了一个类,其对象的属性被屏蔽,就像你需要的一样(可以动态创建)。正如我所说的,它只是在概念证明状态 - 但我认为最常见的错误(如试图访问一个范围内的变量,它根本没有定义)应该有错误,即使不是正确的(IndexError由于堆栈溢出而不是AttributeError的,例如)
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()
动态范围界定被认为是有害的。
请勿使用它;不要模仿它。
如果您需要模拟它,请定义一个dynamic_scope
模块来模拟此行为并将模块导入到所有源文件中。该模块应具有方法begin
,它在函数的第一行中使用动态范围end
,get
和set
。 get
和set
方法应实现查找呼叫链的变量名称,其中调用链由begin
和end
实施。然后重构您的代码以消除动态范围。
动态范围*可以*在支持它的语言中是非常有用的功能。我对大型Common Lisp程序做了很小的(3-4行)更改,如果没有它,将会花费巨大(但机械上很简单)的修改。有时候这是解决问题的自然办法。也就是说,这在Python中并不是很自然,我不会建议直接移植它 - 这看起来似乎是维护痛苦的秘诀。 – Ken 2010-01-05 19:13:45
对于动态范围设定有很好的用处,特别是在设置相对全局的设置时,您不希望通过每个函数的参数(例如,打印stdout的输出的位置)进行线索。当然,动态范围的变量应该被很好地标记,并且它们的一般用法是不鼓励的。下面的Jason Orendorff的解决方案是Python的一个很好的折衷方案,它有助于简化我的一些代码。 – Winterstream 2011-07-19 11:54:50
这里的一些作品有点像Lisp的特殊变量,但适合更好一点成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()
您可以使用它像这样:
with env.let(bufsize=8192, encoding="ascii"):
print env.bufsize # prints 8192
a() # call a function that uses env.bufsize or env.encoding
最后的with
块的持续时间的env.let
影响。
请注意,如果你使用线程,你肯定会想为每个线程使用不同的_stack
。你可以使用threading.local来实现。
这是为了在“不这样做”和堆栈检查之间达成妥协(这看起来好像很慢,很难验证)。 – 2010-01-04 20:59:22
不错的解决方案。它非常明确(所以即使它让某人感到惊讶,也不会妨碍普通的Python语义)。 我发现这种方法对于科学绘图非常有用,因为我想在调用堆栈中的某个位置设置很多设置,并且很难将它们一直带到函数的地方实际绘图发生。 – Winterstream 2011-07-19 11:46:33
与Lisp“特殊”或动态范围变量相对应的Python成语是“线程本地存储”。
这里是一个很好的讨论:What is "thread local storage" in Python, and why do I need it?
如果你想完全效仿Lisp的特殊变量,包括let语句,你可以使用一个上下文管理器:
from __future__ import with_statement # if Python 2.5
from contextlib import contextmanager
import threading
dyn = threading.local()
@contextmanager
def dyn_vars(**new):
old = {}
for name, value in new.items():
old[name] = getattr(dyn, name, None)
setattr(dyn, name, value)
yield
for name, value in old.items():
setattr(dyn, name, value)
例(公然傻,但它显示了可重入特征):
def greet_self():
print 'Hi', dyn.who_am_I
def greet_selves():
with dyn_vars(who_am_I='Evil Twin'):
greet_self()
greet_self()
with dyn_vars(who_am_I='Tobia'):
greet_selves()
恭喜!感谢您的辛勤工作,编程世界还有一个解决方案将深入众多关键应用程序的心中! – yfeldblum 2010-01-04 20:26:49
毕竟,Lisp的“特殊变量”并不是那么可怕,是吗?它们就像bash中的环境变量。可怕的是动态范围设定是默认的语言。幸运的是,其中没有很多被留下。 – 2010-01-04 20:35:31
我很高兴大多数真正的语言不使用动态范围......但我写了大量的Emacs Lisp,对我来说感觉完全自然。 (Emacs Lisp最近将词法作用域作为一个选项,我甚至从来没有打扰过它的使用:-) – offby1 2014-10-19 01:33:27