将变量注入调用者的作用域中?

18 投票
3 回答
4293 浏览
提问于 2025-04-15 20:50

我可以定义一个函数,当这个函数被调用时,能够在调用它的地方插入新的局部变量吗?我觉得把调用者的 locals() 传给这个函数可能可以实现,但有没有办法做到我想要的 而不需要 这样做呢?

3 个回答

0

这件事不可能做到,因为CPython在函数调用期间会保留对参数列表的引用。

一种解决方法是传递一个对象,然后在使用完这个对象后清空它的内部状态。

class Obj(object):
    def __init__(self, state):
        self.state = state

def f(o):
    # Do stuff with o.state
    del o.state
21

看看这个 inspect模块,它被minimock用来模拟调用者的作用域。

这段代码应该正好能满足你的需求:

import inspect
def mess_with_caller():
    stack = inspect.stack()
    try:
        locals_ = stack[1][0].f_locals
    finally:
        del stack
    locals_['my_new_function'] = lambda : 'yaaaaaay'


mess_with_caller()
print my_new_function()
>>> Output: 'yaaaaaay'
9

根据Python的规则,你不能直接修改调用你函数的地方的本地变量。在目前的实现中,如果你尝试这样做(比如用Anurag提到的那些“黑魔法”),你不会收到错误提示(虽然我想在未来的版本中加入这个错误检查),但是如果调用者是一个函数的话,你的修改实际上不会生效(如果调用者是模块的顶层代码,那就另当别论了)——调用者的实际本地变量不会受到影响。无论调用者的本地变量是显式传入的,还是通过“黑魔法”获取的,如果你的代码想要保持合理性,它们都应该被视为只读的字典。

相反,你可以让调用者传入一个明确的、真实的、普通的字典(如果你想的话,可以从locals()初始化这个字典),这样你在这个字典上所做的所有修改都会保留给调用者使用——当然,这些修改不会作为“新的变量名”出现在调用者的本地作用域中,但无论调用者是用x['foo']x.foo,还是(如你所希望的)直接用变量名foo,功能都是一样的。

顺便说一下,如果你想使用属性访问的语法,而不是字典索引的语法,你可以这样做:

class Bunch(object): pass

...

# caller code
b = Bunch()
thefun(b)
print b.foo

...

# called function
def thefun(b):
  b.foo = 23

这也涵盖了一个小变体的情况,即thefun想使用字典索引的语法(比如它的主体是b['foo'] = 23而不是b.foo = 23):在这种情况下,调用者只需要使用thefun(vars(b))而不是简单的thefun(b),但之后它仍然可以继续使用b.foo的访问语法。

撰写回答