在类方法中嵌套函数调用locals()

4 投票
4 回答
10540 浏览
提问于 2025-04-16 22:34

这里有一段代码:

class Foo(object):
    def bar(self, x):
        def baz(y):
            print locals()['self']
        ..
        return baz(..)

foo = Foo()
foo.bar(..)

我有两个盒子。在Windows Vista上,用Python 2.5调用foo.bar(..)是可以正常工作的。但在Ubuntu上,用Python 2.7调用foo.bar(..)时却出现了KeyError错误,因为在本地的字典中找不到self

有没有什么想法?

补充说明:我需要道歉;在尝试描述问题时,我似乎误导了你们。实际上,嵌套函数中的代码是在处理来自DSL的字符串:

            r = re.compile(r'\$(\w+)')
            eval_string = r.sub(r'self.player.stats["\1"]', s)
            result = eval(eval_string)

在Vista/Python 2.5上运行正常,但在Ubuntu/Python 2.7上失败。

4 个回答

0

局部变量总是在当前调用的函数内部定义的变量,而全局变量总是在程序最上面定义的变量。在bar函数中,self是一个局部变量。在baz函数中,self的情况有点复杂,它介于局部变量和全局变量之间。你仍然可以引用它,因为Python会在更高的作用域中查找这个变量,因为它在局部作用域中没有定义,但你无法访问这些作用域的字典。

1

我在使用2.6.6版本时,遇到了和你在2.7中报告的相同情况。不过,我在2.5.2和2.4.6版本中也遇到了这个问题。(都是在Linux系统上。)

有趣的是:

>>> class Foo(object):
    def bar(self, x):
        def baz(y):
            print self.__class__.__name__
            print locals()['self']
        return baz(4)
...
>>> Foo().bar(3)
Foo
<__main__.Foo object at 0x9801f4c>
>>>

可以推测,可能对计算本地符号表的过程进行了某种优化或修改,这样只有在特定函数范围内实际使用的名称才会被添加到符号表中。即使某个符号在被明确提及时是可以访问的(并且不会是全局的),它也不会被加入。

如果把bar()的最后一行改成return baz(而不是return baz(4)),我们可以看到self并不是一个局部变量;它是一个自由变量:

>>> baz = Foo().bar(4)
>>> baz.func_code.co_nlocals, f.func_code.co_varnames, f.func_code.co_freevars
(1, ('y',), ('self',))

不过,即使在这里,x也没有被算作自由变量,因为当名称没有在内部函数中使用时,它不会被算作自由变量

不过,这个解释让你在Vista上使用2.5时描述的行为听起来像是一个bug。

1

如果你承认在运行坏代码之前可以计算出变量字典,那你可以使用这个:

class Foo(object):
    def bar(self, x):
        outer_locals = locals()
        def baz(y):
            print outer_locals['self']
        ..
        return baz(..)

但是,如果你必须在运行时(也就是在bad函数里面)计算这个字典,那就应该这样做:

import inspect

def outer_locals(depth=0):
    """
    With depth=0 behaves like locals(), depth=1 are the locals of the
    containing frame, depth=2 the locals of the frame containing the containing
    frame and so on...
    """
    return inspect.getouterframes(inspect.currentframe())[depth+1][0].f_locals

class Foo(object):
    def bar(self, x):
        def baz(y):
            print outer_locals(1)
        return baz(...)

注意,如果在baz函数中没有覆盖的话,bar函数里的所有局部变量在bad函数中都是可以用的:

class Foo(object):
    def bar(self, x):
        def baz(y):
            print self
        ..
        return baz(..)

撰写回答