2010-01-04 85 views

回答

11

我觉得正义在他的推理中是正确的。另一方面 - 我无法抗拒对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() 
+0

恭喜!感谢您的辛勤工作,编程世界还有一个解决方案将深入众多关键应用程序的心中! – yfeldblum 2010-01-04 20:26:49

+0

毕竟,Lisp的“特殊变量”并不是那么可怕,是吗?它们就像bash中的环境变量。可怕的是动态范围设定是默认的语言。幸运的是,其中没有很多被留下。 – 2010-01-04 20:35:31

+1

我很高兴大多数真正的语言不使用动态范围......但我写了大量的Emacs Lisp,对我来说感觉完全自然。 (Emacs Lisp最近将词法作用域作为一个选项,我甚至从来没有打扰过它的使用:-) – offby1 2014-10-19 01:33:27

-5

动态范围界定被认为是有害的。

请勿使用它;不要模仿它。

如果您需要模拟它,请定义一个dynamic_scope模块来模拟此行为并将模块导入到所有源文件中。该模块应具有方法begin,它在函数的第一行中使用动态范围end,getsetgetset方法应实现查找呼叫链的变量名称,其中调用链由beginend实施。然后重构您的代码以消除动态范围。

+3

动态范围*可以*在支持它的语言中是非常有用的功能。我对大型Common Lisp程序做了很小的(3-4行)更改,如果没有它,将会花费巨大(但机械上很简单)的修改。有时候这是解决问题的自然办法。也就是说,这在Python中并不是很自然,我不会建议直接移植它 - 这看起来似乎是维护痛苦的秘诀。 – Ken 2010-01-05 19:13:45

+2

对于动态范围设定有很好的用处,特别是在设置相对全局的设置时,您不希望通过每个函数的参数(例如,打印stdout的输出的位置)进行线索。当然,动态范围的变量应该被很好地标记,并且它们的一般用法是不鼓励的。下面的Jason Orendorff的解决方案是Python的一个很好的折衷方案,它有助于简化我的一些代码。 – Winterstream 2011-07-19 11:54:50

11

这里的一些作品有点像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来实现。

+3

这是为了在“不这样做”和堆栈检查之间达成妥协(这看起来好像很慢,很难验证)。 – 2010-01-04 20:59:22

+2

不错的解决方案。它非常明确(所以即使它让某人感到惊讶,也不会妨碍普通的Python语义)。 我发现这种方法对于科学绘图非常有用,因为我想在调用堆栈中的某个位置设置很多设置,并且很难将它们一直带到函数的地方实际绘图发生。 – Winterstream 2011-07-19 11:46:33

5

与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()