logo

Scope (3)

入门。

这是一段很简单的 Bash 脚本。x 初始值为 0,foo打印出 x 的值,bar 将 x 设为 100 并调用 foo。

x=0

foo() {
    echo "foo: $x"
}

bar() {
    echo -n "bar: "
    local x=100;
    foo
}

foo
bar
foo

结果不出意外,foo 打印出 0,而 bar 打印出 100

foo: 0
bar: foo: 100
foo: 0

如果把 bar 里的 local x=100; 的 local 去掉,那全局的 x 的值会被改写,打印的结果就是

foo: 0
bar: foo: 100
foo: 100

不难理解吧?

放弃?

如果把这段代码翻译成 Python 呢?

x = 0


def foo():
    print("foo: %d" % x)


def bar():
    print("bar: ", end="")
    x = 100
    foo()


foo()
bar()
foo()

运行的结果编程了:

foo: 0
bar: foo: 0
foo: 0

这跟两次 Bash 运行的结果都不一样,foo 打印出的 x 的值为什么始终为 0?

进阶!

Scope 分两种,一种是静态的,叫 static scope 或 lexical scope,另一种是动态的,叫 dynamic scope。所谓的“静态”,就是在编译的时候就已经确定了的,而“动态”是指运行的时候再决定变量的值。Python 和大部分现代语言都采用 lexical scope,而 bash, LISP 等语言采用 dynamic scope,此外 Perl 两种都支持。

以此为例,Python 代码在编译的时候就将 foo 中的 x 绑定到全局变量 x(所谓的 early binding),所以无论 foo 何时被调用,打印出来的都是 0. 一个小实验是把最后函数调用的部分改成如下:

foo()
x = 999
foo()
bar()
foo()

结果是:

foo: 0
foo: 999
bar: foo: 999
foo: 999

可见全局变量 x 改变了,所有的 foo 的打印值都变了。

而在 Bash 的代码中,编译时 x 的值并没有被绑定,(所以也叫 late binding),当 foo 第一次被调用的时候,它才去找 x 的定义,所以采用了 x=0;当 bar 被调用的时候,foo 找到的是 bar 里定义的 x=100,所以打印出来 100;如果没用 local 关键字,那全局的 x 被改动,当下一次 foo 被直接调用的时候,它还是找到了全局变量 x,打印出来就是被更新后的值 100.