runsisi's

technical notes

Python 嵌套函数同名变量作用域

2019-03-03 runsisi#python

Python 嵌套函数间同名变量的作用域规则与 C++/Go/JS 稍有不同,对于读操作而言,Python 的行为与 C++/Go/JS 一致,通过 global/nonlocal 关键字还能做到更灵活,赋值操作则必须要使用 global/nonlocal 关键字才能实现对外层/全局变量的引用。

不过需要注意的是,在函数内部 Python 无法定义同名的局部变量,当然 C++/Go/JS 也无法做到在内层函数直接引用同名的全局变量,这是 Python 与 C++/Go/JS 非常大的不同。

C++

#include <iostream>
  
using namespace std;

int x = 0;

void outer() {
    x = 1;

    [&] { // middle
        [&] { // inner
            x = 2;
            { // local
                int x = 3;
                cout << "local:\t" << x << endl;
            }
            cout << "inner:\t" << x << endl;
        }();
        cout << "middle:\t" << x << endl;
    }();

    cout << "outer:\t" << x << endl;
}

int main()
{
    outer();
    cout << "global:\t" << x << endl;

    return 0;
}

输出

~$ g++ -o var -std=c++11 var.cc
~$ ./var
local:	3
inner:	2
middle:	2
outer:	2
global:	2

Go

package main

import "fmt"

var x int = 0

func outer() {
    x = 1
    func() { // middle
        func() { // inner
            x = 2
            { // local
                x := 3
                fmt.Println("local:\t", x)
            }
            fmt.Println("inner:\t", x)
        }()

        fmt.Println("middle:\t", x)
    }()

    fmt.Println("outer:\t", x)
}

func main() {
    outer()
    fmt.Println("global:\t", x)
}

输出

~$ go run var.go
local:	 3
inner:	 2
middle:	 2
outer:	 2
global:	 2

JS(ES6)

#!/usr/bin/env node

let x = 0

outer = () => {
    x = 1
    middle = () => {
        inner = () => {
            x = 2
            { // local
                let x = 3
                console.log("local:\t", x)
            }
            console.log("inner:\t", x)
        }
        
        inner()
        console.log("middle:\t", x)
    }

    middle()
    console.log("outer:\t", x)
}

outer()
console.log("global:\t", x)

输出

~$ ./var.js
local:	 3
inner:	 2
middle:	 2
outer:	 2
global:	 2

Python2

Python2 提供了 global 关键字可以在内层函数中显式引用全局变量,如果不使用 global,内层函数的读操作将引用外层函数的变量(如果没有外层函数,则引用全局变量),赋值操作将定义新变量,赋值操作的行为无法做到与 C++/Go/JS 保持一致(global 需要在首次引用同名变量前定义)。

#! /usr/bin/env python2

x = 0

def outer():
    global x
    x = 1
    def middle():
        def inner():
            x = 2
            if True:
                x = 3
                print('local:\t{0}'.format(x))
            print('inner:\t{0}'.format(x))

        inner()
        print('middle:\t{0}'.format(x))

    middle()
    print('outer:\t{0}'.format(x))

outer()
print('global:\t{0}'.format(x))

输出

~$ ./var2.py 
local:	3
inner:	3
middle:	1
outer:	1
global:	1

Python3

Python3 提供了新的关键字 nonlocal 用于显式引用外层函数的变量(nonlocal 需要在首次引用同名变量前定义),nonlocal 结合 global 使得 Python3 能够做到赋值操作的行为与 C++/Go/JS 保持一致。

#!/usr/bin/env python3

x = 0

def outer():
    x = 1
    def middle():
        nonlocal x
        def inner():
            global x
            x = 2
            if True:
                x = 3
                print('local:\t{0}'.format(x))
            print('inner:\t{0}'.format(x))

        inner()
        print('middle:\t{0}'.format(x))

    middle()
    print('outer:\t{0}'.format(x))

outer()
print('global:\t{0}'.format(x))

输出

~$ ./var3.py 
local:	3
inner:	3
middle:	1
outer:	1
global:	3

注意 nonlocal 不能引用全局变量。

#!/usr/bin/env python3

x = 0

def outer():
    global x
    x = 1
    def middle():
        nonlocal x
        def inner():
            global x
            x = 2
            if True:
                x = 3
                print('local:\t{0}'.format(x))
            print('inner:\t{0}'.format(x))

        inner()
        print('middle:\t{0}'.format(x))

    middle()
    print('outer:\t{0}'.format(x))

outer()
print('global:\t{0}'.format(x))

输出

~$ ./var3.py 
  File "./var3.py", line 9
    nonlocal x
    ^
SyntaxError: no binding for nonlocal 'x' found

参考资料

Scope of Variables in Python Tutorial

https://www.datacamp.com/community/tutorials/scope-of-variables-python