不构成作用域

在 C 语言中, for 或者 if 是构成作用域的,在里面声明的变量是无法在外面使用的:

for (int i = 0; i < 3; i++) {
    int a = i;
}
printf("i: %d, a: %d\n", i, a); // error
 
if (1) {
    int a = 3;
}
 
printf("a: %d\n", a); // error

但是在 python 中并没有这种规则,在 if/elif/else, for/while, try/except/finally/with 块都不构成作用域,所以你可以看到如下代码:

if True:
    a = 3
 
print(a) # 3

在 python 中,只有函数、类、模块(也就是文件)会形成新的作用域。

四种作用域

python 中有四种作用域:

  • Local:当前函数的局部作用域。
  • Enclosing:包含当前函数的外部函数的作用域(如果有嵌套函数)。
  • Global:当前模块的全局作用域。
  • Built-in:Python 内置的作用域。

查找时按从内往外的顺序查找:

示例如下:

g_count = 0  # Global
def outer():
    o_count = 1  # Enclosing
    def inner():
        i_count = 2  # Local

global/nonlocal

如果按照上面的规则,再配合“python 赋值时才声明”的语法特性,会发现一个有意思的“语义无能”现象:

global_value = 1
 
def write_global_value():
    global_value = 2
 
def read_global_value():
    print(global_value)
 
write_global_value()
read_global_value() # 为什么不是 2 而是 1

这个如果写成 C ,那么显然应该打印 2 ,我们写了一个函数来更新全局变量,更新完了自然应该是 2 。就算是 python ,我并没有在 write_global_value 中声明 global_value 这个变量,它应该自动引用外部变量呀,在 read_global_vaule 中就很好得读出了全局变量的值呀?

这是因为当我们在函数中使用赋值语句 global_value = 2 时,其本质不是引用了全局变量,而是创造了一个新的局部变量,只是名字恰好和全局变量“撞车”了。

然后我们发现,当我们想用一个函数去写全局变量,就需要引入新的关键字了,他们就是 global/nonlocal ,分别用于引用全局变量,和引用 enclosing 变量。示例如下:

global_value = 1
 
def write_global_value():
    global global_value
    global_value = 2
 
def read_global_value():
    print(global_value)
 
write_global_value()
read_global_value() # 2

nonlocal:

def outer():
    num = 10
    def inner():
        nonlocal num
        num = 100
        print(num)
    inner()
    print(num)
outer()