在Python中访问调用函数的变量

Jen*_*ens 11 python python-3.x

我怀疑我想做的事情在Python中不是那么干净.以下是一些相互调用的嵌套函数.(通常,它们不必是词法范围的,但需要动态地相互调用.)

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

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

    inner()
Run Code Online (Sandbox Code Playgroud)

现在,当我打电话do_something(),然后我想访问的调用函数的变量进一步向上调用堆栈,在这种情况下s_outers_inner.

不幸的是,nonlocal这里的关键字只有在我定义函数do_something()内部时才有帮助inner().但是,如果我在同一级别定义它,outer()nonlocal关键字将不起作用.

但是,我想do_something()从各种其他函数调用,但始终在各自的上下文中执行它并访问它们各自的范围.

感觉顽皮我然后写了一个小的访问者,我可以从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 
Run Code Online (Sandbox Code Playgroud)

然后

def do_something() :
    print( reach("s_outer"), reach("s_inner") )
Run Code Online (Sandbox Code Playgroud)

工作得很好.

我的两个问题是这些

  1. 有没有更好的方法来解决这个问题?(除了将各自的数据包装成dicts并明确地将这些dicts传递给do_something())

  2. 是否有更优雅/缩短的方式来实现该reach()功能?

干杯!

ber*_*eal 7

没有,在我看来,应该没有优雅的实现方式,reach因为它引入了一种新的非标准间接方式,它真的很难理解、调试、测试和维护。正如 Python 口头禅 (try import this) 所说:

显式优于隐式。

所以,只需传递参数。未来的你将从今天开始真的很感激你。


Jen*_*ens 6

我最终做的是

scope = locals()
Run Code Online (Sandbox Code Playgroud)

scopedo_something. 这样我就不必访问了,但我仍然可以访问调用者的局部变量字典。这与自己构建字典并将其传递非常相似。


mtr*_*eur 6

我们可以变得更顽皮。

reach()这是对“是否有更优雅/更简短的方式来实现该功能?”的回答。问题的一半。

  1. 我们可以为用户提供更好的语法:而不是reach("foo"), outer.foo

    这更适合键入,并且语言本身会立即告诉您是否使用了不能是有效变量的名称(属性名称和变量名称具有相同的约束)。

  2. 我们可以提出一个错误,以正确区分“这不存在”和“这已设置为None”。

    如果我们真的想将这些情况混合在一起,我们可以getattr使用默认参数或try- except AttributeError

  3. 我们可以优化:不需要悲观地一次性构建一个足够大的列表来容纳所有帧。

    在大多数情况下,我们可能不需要一路走到调用堆栈的根部。

  4. 仅仅因为我们不恰当地到达堆栈帧,违反了最重要的编程规则之一,即不让远处的事物无形地影响行为,并不意味着我们不能文明。

    如果有人尝试在没有堆栈帧检查支持的情况下在 Python 上使用这个 Serious 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()
Run Code Online (Sandbox Code Playgroud)

出色的。现在我们可以这样做:

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

变态优雅地实现了。

(PS这是我的库中更完整的实现的简化只读版本dynamicscope。)