某天,一位新来的同事问我,在 Python 的函数中,怎么定义一个像 C 语言中的 static 变量。就像这样:

void foo(void)
{
    static int a = 0;

    ...
}

已经抛弃 C 好长时间了,现在工作和业余都在使用 Python,也习惯用 Python 去思考。被问到这个问题时,我一时还真不知道怎么回答,因为在使用 Python 的这么长时间里,我还真没有遇到过这样的需求。当然,Python 自身也不直接支持类似的语法,所以也不会这样去思考。

实际上,他是想要在函数中保持一个变量的状态。经过短暂的思考,我回答他,你可以定义一个全局变量,然后在函数中用 global 引用这个变量就可以。顺便我还告诉他,Python 没有原生的 static 变量支持,你应该换一下别的思路,程序并不一定非要这么设计。

过了一会,他又来找我,说用 global 关键字后程序有问题。无赖之下,只能看了看他写的代码,其大概是像下面这样:

class Foo(object):
    def too(self):
        l = 1
        def moo():
            global l
            l = l + 1
            print l
        moo()
        print l

f = Foo()
f.too()

我告诉他,你把 global 那一句去掉就可以了。过了一会他又说,还是不行。这个时候,我已经有些不耐烦了,怎么就不行了呢。于是我调试了上面代码,报错:

UnboundLocalError: local variable 'l' referenced before assignment

我马上意识到我忽略了一个问题,这里是一个嵌套名字空间。我的同事并没有定义一个全局变量,而是一个局部变量,但他却尝试用 global 关键字去引用,显然得到的结果会是全局变量没有定义。所以我告诉他,去掉 global 语句。我没有意识到里边还有一个嵌套的作用域,之前从来没有没有遇到过这种问题,所以也没太注意。

如果把程序改成下边这样,则可以正常运行:

class Foo(object):
    def too(self):
        l = 1
        def moo():
            print l
        moo()
        print l

f = Foo()
f.too()

当时我并没有意识到问题出在哪里,于是查了下资料,发现了如下的一些规则

  • 内部函数,不能修改全局变量但可以访问全局变量
  • 内部函数,在尝试修改同名全局变量时,Python 会认为它是一个局部变量,并引发 UnboundLocalError
  • 在内部函数修改同名全局变量之前尝试访问变量名称(如: print l; l = l + 1),也会引发 UnboundLocalError

所以问题涉及到一个作用域层次的问题,也就是 内层作用域可以访问外层作用域中的变量,但不能修改,一旦尝试修改,Python 则会认为该变量是一个局部变量。这样,问题就被解释了。Python 3 中的引入了 nonlocal 关键字来解决闭包中的变量作用域问题。

还需要注意的一点是,在 Python 中,只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的,即使有不同层次的缩进。

我没有仔细看同事的代码,不知道他具体要表达什么,我只是告诉他换一种设计思路。我觉得很多时候我们都应该这样,当你发现你的设计思路不符合语言习惯的时候,那说明你的设计是不正确,何不换换别的思路呢。