一、命名空间
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、命名空间的生命周期
命名空间在不同时刻被创建,拥有不同的生存期,如果对象执行完成,则该命名空间的生命周期就结束。
- 内置名称的命名空间是在 Python 解释器启动时创建的,永远不会被删除。
- 模块的全局命名空间在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出。
- 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。当然,每次递归调用都会有它自己的本地命名空间。
二、作用域
1、定义
作用域:是一个命名空间可直接访问的 Python 程序的文本区域,就是变量在程序里面的可应用范围。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。
作用域一共有四种:
- L(Local):最内层,包含局部变量,比如一个函数/方法内部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
- G(Global):当前脚本的最外层,比如当前模块的全局变量。
- 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变量。
那么,在内部作用域怎么修改外部作用域的变量呢?
这就要用到 global 或 nonlocal 了。
(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去改变一个在全局中未定义的变量时会直接穿甲一个全局变量。