请看这个例子:
def outer():
s_outer = "outer\n"
def inner():
s_inner = "inner\n"
do_something()
inner()
我希望do_something
中的代码能够访问调用堆栈中调用函数的变量,在本例中是s_outer
和s_inner
。更一般地说,我希望从各种其他函数调用它,但总是在它们各自的上下文中执行它,并访问它们各自的作用域(实现 * 动态作用域 *)。
我知道在Python 3.x中,nonlocal
keyword allows access到s_outer
是从inner
中定义的,不幸的是,只有当do_something
在inner
中定义时,这才对do_something
有帮助,否则,inner
就不是一个 * 词法上 * 封闭的作用域(类似地,outer
也不是,除非do_something
在outer
中定义)。
我想出了如何使用标准库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
能不能实现得更简单一些?还有什么办法可以解决这个问题?
4条答案
按热度按时间qacovj5a1#
在我看来,没有,也不应该有优雅的方式来实现
reach
,因为这引入了一个新的非标准间接寻址,这真的很难理解、调试、测试和维护,正如Python的咒语(尝试import this
)所说:显性比隐性好。
所以,不要争论了,来自未来的你会非常感激来自今天的你。
chhkpiq42#
我最后做的是
并使
scope
可以从do_something
访问,这样我就不必访问,但仍然可以访问调用方的局部变量字典,这与自己构建字典并传递它非常相似。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,我们应该让他们知道。
很好。现在我们可以做:
完美的变态。
(P.S.这是我的
dynamicscope
库中一个更完整实现的简化只读版本。)yc0p9oo04#
有没有更好的方法来解决这个问题呢?(除了将相应的数据 Package 成dicts并将这些dicts显式地传递给do_something()之外)
明确地传递指令是一种更好的方式。
你的建议听起来很不传统。当代码增加时,你必须把代码分解成模块化的架构,模块之间有干净的API。它还必须是容易理解、容易解释、容易交给另一个程序员修改/改进/调试的东西。你的建议听起来像是一个 * 不 * 干净的API,不传统,有一个不明显的数据流。我怀疑这可能会使许多程序员看到它时脾气暴躁。:)
另一种选择是将函数作为类的成员,而数据则在类示例中,如果你的问题可以被建模为对数据对象进行操作的多个函数,那么这种方法就可以很好地工作。