如何在Python中创建动态范围变量?

2024-06-16 13:39:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在将一些代码从lisp翻译成Python。在

在lisp中,可以使用一个let构造,其中引入的变量声明为特殊变量,从而具有动态范围。(见http://en.wikipedia.org/wiki/Dynamic_scope#Dynamic_scoping

如何在Python中实现同样的操作?似乎语言并不直接支持这一点,如果是真的,有什么好的方法来模仿它呢?在


Tags: 方法代码org语言http声明wiki动态
3条回答

对应于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)

示例(显然很傻,但它显示了可重入特性):

^{pr2}$

我觉得他的推理是公正的。在

另一方面,我忍不住为另一个编程范式实现概念证明,这对Python来说是“不自然的”——我只是喜欢这样做。:-)

因此,我创建了一个类,它的对象属性的范围与您需要的一样(并且可以动态创建)。正如我所说,它只是处于概念验证状态-但我认为大多数常见的错误(如试图在根本没有定义的范围内访问变量)应该引发错误,即使不是正确的错误(例如,由于堆栈下溢而不是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()

这里有一些类似于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()

你可以这样使用它:

^{pr2}$

env.let的影响持续到with块的持续时间。在

请注意,如果您使用线程,您肯定需要为每个线程使用不同的_stack。您可以使用threading.local来实现它。在

相关问题 更多 >