python作用域和命名空间

249 阅读5分钟

一、命名空间

1、命名空间定义

命名空间(namespace):是一个从名称到对象的映射。 当前大部分命名空间都由 Python 字典实现(A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。)。从某种意义上说,对象的属性集合也是一种命名空间的形式。不同命名空间中的名称之间绝对没有关系,相同的对象名称可以存在于多个命名空间中。

三种命名空间:

1. 内置命名空间(built-in):存放内置函数的集合(包含 abs() 这样的函数,和内建的异常等);
2. 全局命名空间(global):模块中的全局名称;记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
3. 局部命名空间(local):函数调用中的局部名称,记录了函数的变量,包括函数的参数和局部定义的变量。

Python的查找顺序和大多数编程语言一样,都是逐层向上的。具体顺序为:局部的命名空间 >> 全局命名空间 >> 内置命名空间

2、命名空间的生命周期

命名空间在不同时刻被创建,拥有不同的生存期,如果对象执行完成,则该命名空间的生命周期就结束。

  1. 内置名称的命名空间是在 Python 解释器启动时创建的,永远不会被删除。
  2. 模块的全局命名空间在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出。
  3. 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。当然,每次递归调用都会有它自己的本地命名空间。

二、作用域

1、定义

作用域:是一个命名空间可直接访问的 Python 程序的文本区域,就是变量在程序里面的可应用范围。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。

作用域一共有四种:

  1. L(Local):最内层,包含局部变量,比如一个函数/方法内部。
  2. E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
  3. G(Global):当前脚本的最外层,比如当前模块的全局变量。
  4. B(Built-in): 包含了内建的变量/关键字等。

2、访问顺序

在访问变量时:最先搜索的最内部作用域包含局部名称。然后搜索当前函数或方法的上一层的内部作用域的名称,这类可能包含多层嵌套的函数或方法。倒数第二个作用域包含当前模块的全局名称。最外面的作用域(最后搜索)是包含内置名称的命名空间。 如下:

abs = 1
print(abs) # 结果:1
def f1():
    abs = 5        
    print(abs) # 结果:5
    def f2():
        abs = 'abs'
        print(abs) # 结果:abs
    f2();
f1()

上面这段代码分别在全局作用域、f1函数作用域、f2作用域内部都定义了一个abs,而内置作用域本来有一个abs函数变量。在三个作用域里面分别打印了abs,得到的结果是分别在三个作用域里面赋的值,这说明了程序在执行时首先在当前作用域里搜索,在当前作用域找不到变量时才会往上一层去搜索。

abs = 1
def f1():
    abs = 5
    def f2():
        print(abs) # 结果:5
    f2();
f1()

同样的一段代码,这次我们没有在f2函数里面定义一个abs的变量,输出的结果就是f1函数里面定义的abs这个变量的值。当我们未在f1函数里面定义abs时,打印的值就会是全局变量的值。如果全局变量也没有定义,则会返回 。而这个正是 Python 内置的变量。

3、global 和 nonlocal

在上面说明了作用域的查找顺序,可以发现在一个作用域的内部是可以访问外部作用域的变量的。但是如果我们要修改外部作用域的值呢? 看下面这段代码:

def f1():
    num1 = 1
    def f2():
        num1 = 2    
        print(num1) # 结果:2
    f2();
    print(num1) # 结果:1
f1()

可以看到上面的结果,在执行了f2函数之后,函数f1里面的num1值没有变化。这是因为在f2函数里面执行 num1 = 2 时,实际上是在f2里面重新定义了一个num1变量,而不是修改的f1里面的num1变量。

那么,在内部作用域怎么修改外部作用域的变量呢?

这就要用到 globalnonlocal 了。

(1)nonlocal

nonlocal用于修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量。

def f1():
    num1 = 2
    def f2():
        nonlocal num1
        num1 = 3    
    f2();
    print(num1) # 结果:3
f1()

上面返回的结果显示在f2内部成功的修改了定义在f1里面的变量num1。

(2)global

global用于内部作用域想修改全局作用域的变量。

num1 = 1
def f1():
    global num1
    num1 = 2
    print(num1) # 结果:2
f1()
print(num1) # 结果:2

上面返回的结果显示在f1内部成功的修改了定义的全局变量num1。

注:

  • 使用nonlocal识图去改变全局变量时,会直接报错:No binding for nonlocal "num1" found。
  • 使用global去改变一个在全局中未定义的变量时会直接创建一个全局变量。