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

Wil*_*mKF 12 lisp python variables scope dynamic-scope

我正在将一些代码从lisp转换为Python.

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

我怎样才能在Python中做同样的事情?看来语言不直接支持这个,如果是真的,那么什么是模仿它的好方法呢?

jsb*_*eno 11

我认为正义在他的推理中是明确的.

另一方面 - 我无法抗拒实现对Python的另一种编程范式"不自然"的概念证明 - 我只是喜欢这样做.:-)

所以,我创建了一个类,其对象的属性就像你需要的那样被scopped(并且可以动态创建).正如我所说,它只是在概念证明状态 - 但我认为最常见的错误(如试图访问范围内的变量,它根本没有定义)应该会引发错误,即使不是正确的错误(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()
Run Code Online (Sandbox Code Playgroud)


Jas*_*rff 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()
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

with env.let(bufsize=8192, encoding="ascii"):
    print env.bufsize  # prints 8192
    a()  # call a function that uses env.bufsize or env.encoding
Run Code Online (Sandbox Code Playgroud)

env.let最后一次对with块的持续时间的影响.

请注意,如果您使用线程,您肯定希望_stack每个线程都有不同的线程.您可以使用threading.local来实现它.

  • 这是作为"不要那样做"和堆栈检查之间的折衷(看起来它很慢并且难以验证). (3认同)
  • 好的解决方案 这是非常明确的(所以即使它让人惊讶,它也不会破坏普通的Python语义).我发现这种方法对于科学绘图非常有用,因为我想在调用堆栈中的某个点设置许多设置,并且必须将它们一直带到实际的函数中是一件痛苦的事情.密谋发生. (2认同)

Tob*_*bia 6

对应于Lisp"特殊"或动态范围变量的Python习语是"线程本地存储".

这是一个很好的讨论:什么是Python中的"线程本地存储",为什么需要它?

如果要完全模拟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)
Run Code Online (Sandbox Code Playgroud)

示例(显然是愚蠢的,但它显示了可重入的特征):

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()
Run Code Online (Sandbox Code Playgroud)

  • 对于需要与“异步”代码(以及线程代码)兼容的 Python 3.7+,您可以[使用“contextvars”](https://docs.python.org/3/library/contextvars.html#module- contextvars)而不是线程局部变量,[它允许您将上下文状态与任务(所有任务都在同一线程中运行)相关联,而不仅仅是线程。](https://www.python.org/dev/peps/pep-0567 /)。 (3认同)