2010-01-04 91 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()