python-3.x 如何从调用方访问变量,即使它不是一个封闭作用域(即实现动态作用域)?

pftdvrlh  于 2023-03-04  发布在  Python
关注(0)|答案(4)|浏览(128)

请看这个例子:

def outer():
    s_outer = "outer\n"

    def inner():
        s_inner = "inner\n"
        do_something()

    inner()

我希望do_something中的代码能够访问调用堆栈中调用函数的变量,在本例中是s_outers_inner。更一般地说,我希望从各种其他函数调用它,但总是在它们各自的上下文中执行它,并访问它们各自的作用域(实现 * 动态作用域 *)。
我知道在Python 3.x中,nonlocal keyword allows accesss_outer是从inner中定义的,不幸的是,只有当do_somethinginner中定义时,这才对do_something有帮助,否则,inner就不是一个 * 词法上 * 封闭的作用域(类似地,outer也不是,除非do_somethingouter中定义)。
我想出了如何使用标准库inspect检查堆栈帧,并创建了一个可以从do_something()中调用的小访问器,如下所示:

def reach(name):
    for f in inspect.stack():
        if name in f[0].f_locals:
            return f[0].f_locals[name]
    return None

然后

def do_something():
    print( reach("s_outer"), reach("s_inner") )

效果很好。
reach能不能实现得更简单一些?还有什么办法可以解决这个问题?

qacovj5a

qacovj5a1#

在我看来,没有,也不应该有优雅的方式来实现reach,因为这引入了一个新的非标准间接寻址,这真的很难理解、调试、测试和维护,正如Python的咒语(尝试import this)所说:
显性比隐性好。
所以,不要争论了,来自未来的你会非常感激来自今天的你。

chhkpiq4

chhkpiq42#

我最后做的是

scope = locals()

并使scope可以从do_something访问,这样我就不必访问,但仍然可以访问调用方的局部变量字典,这与自己构建字典并传递它非常相似。

6psbrbz9

6psbrbz93#

我们可以更淘气。
这回答了“是否有更优雅/更简短的方法来实现reach()函数?”这个问题的一半。
1.我们可以为用户给予更好的语法:而不是reach("foo")outer.foo
这对于类型来说更好,并且语言本身会立即告诉您是否使用了一个不可能是有效变量的名称(属性名称和变量名称具有相同的约束)。
1.我们可以引发一个错误,以正确区分“this doesn 't exist”和“this was set to None“。
如果我们真的想把这些情况涂抹在一起,我们可以使用默认参数getattr,或者try-except AttributeError
1.我们可以优化:不需要悲观地构建一个足够大的列表来同时容纳所有帧。
在大多数情况下,我们可能不需要一直走到调用堆栈的根。
1.仅仅因为我们不适当地向上访问堆栈帧,违反了编程中最重要的规则之一,即不让遥远的东西无形地影响行为,并不意味着我们不能文明。
如果有人试图在没有栈帧检查支持的Python上使用这个严肃的API,我们应该让他们知道。

import inspect

class OuterScopeGetter(object):
    def __getattribute__(self, name):
        frame = inspect.currentframe()
        if frame is None:
            raise RuntimeError('cannot inspect stack frames')
        sentinel = object()
        frame = frame.f_back
        while frame is not None:
            value = frame.f_locals.get(name, sentinel)
            if value is not sentinel:
                return value
            frame = frame.f_back
        raise AttributeError(repr(name) + ' not found in any outer scope')

outer = OuterScopeGetter()

很好。现在我们可以做:

>>> def f():
...    return outer.x
... 
>>> f()
Traceback (most recent call last):
    ...
AttributeError: 'x' not found in any outer scope
>>> 
>>> x = 1
>>> f()
1
>>> x = 2
>>> f()
2
>>> 
>>> def do_something():
...     print(outer.y)
...     print(outer.z)
... 
>>> def g():
...     y = 3
...     def h():
...         z = 4
...         do_something()
...     h()
... 
>>> g()
3
4

完美的变态。
(P.S.这是我的dynamicscope库中一个更完整实现的简化只读版本。)

yc0p9oo0

yc0p9oo04#

有没有更好的方法来解决这个问题呢?(除了将相应的数据 Package 成dicts并将这些dicts显式地传递给do_something()之外)
明确地传递指令是一种更好的方式。
你的建议听起来很不传统。当代码增加时,你必须把代码分解成模块化的架构,模块之间有干净的API。它还必须是容易理解、容易解释、容易交给另一个程序员修改/改进/调试的东西。你的建议听起来像是一个 * 不 * 干净的API,不传统,有一个不明显的数据流。我怀疑这可能会使许多程序员看到它时脾气暴躁。:)
另一种选择是将函数作为类的成员,而数据则在类示例中,如果你的问题可以被建模为对数据对象进行操作的多个函数,那么这种方法就可以很好地工作。

相关问题