我正在將一些代碼從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