def test(): A = 3 B = 4 def stu(): C = 3 return A+B+C return stu stu = test() stu
在上述此代码中stu方法定义在test的内部,内部函数stu即可以使用外部函数test的变量,我们称这种行为叫做闭包。
命名空间 命名空间简述先看看官方文档的一段话:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实施的。
这就是说,命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
一般有三种命名空间:
- 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
- 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
假设我们要使用变量 var,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。如果找不到变量 var,它将放弃查找并引发一个 NameError 异常:
NameError: name 'var' is not defined。命名空间的生命周期
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。因此,我们无法从外部命名空间访问内部命名空间的对象,因为一个完整的内部命名空间已经在运行完成后结束了它的生命周期。如下:
# var1 是全局名称
var1 = 5
def some_func():
# var2 是局部名称
var2 = 6
def some_inner_func():
# var3 是内嵌的局部名称
var3 = 7
如下图所示,相同的对象名称可以存在于多个命名空间中。
还是先看官方文档:
A scope is a textual region of a Python program where a namespace is directly accessible. "Directly accessible" here means that an unqualified reference to a name attempts to find the name in the namespace.
作用域就是一个 Python 程序可以直接访问命名空间的正文区域。这里的“可以直接访问”的意思是一个对一个名称的无限定引用(即与object.name相比,没有object的前缀)会尝试在命名空间中寻找这个名字。
在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
四种作用域变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:
- L(Local):最内层,包含局部变量,比如一个函数/方法内部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
- G(Global):当前脚本的最外层,比如当前模块的全局变量。
- B(Built-in): 包含了内建的变量/关键字等,最后被搜索。
规则顺序: L –> E –> G –> B。在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
不是任何代码块都能引入新的作用域Python中不是任何代码块都可以引入新的作用域,这与C有很大的不同:
在C中:
#includeint main() { if(2 > 0) { int i = 0; } printf("i = %d", i); return 0; }
在上述代码中,if子句引入了一个局部作用域,变量i就存在于这个局部作用域中,但对外不可见,因此,接下来在printf函数中对变量i的引用会引发编译错误。
但是,在Python中却并非如此:
if True:
i = 0
print i
在上述代码中,if子句并没有引入一个局部作用域,变量i仍然处在全局作用域中,因此,变量i对于接下来的print语句是可见的。
作用域的举例说明实际上,在Python中,只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的。
在Python中,使用一个变量之前不必预先声明它,但是在真正使用它之前,它必须已经绑定到某个对象;而名字绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量,不论这个名字绑定发生在当前作用域中的哪个位置。
global i
i = 1
def fn():
print(i)
i = 8
fn()
Traceback (most recent call last):
Python Shell, prompt 27, line 1
# Used internally for debug sandbox under external interpreter
Python Shell, prompt 26, line 2
if __name__ == '__main__':
builtins.UnboundLocalError: local variable 'i' referenced before assignment
由于在fn函数的作用域内,i为局部变量,尽管在外部所申明的变量i为全局变量。但是python进行变量查找的顺序是先局部变量、再全局变量、再内置变量。由于python已经在fn函数的局部找到了i,尽管这个i的赋值发生在调用i之后,但也是报错的。
接着直接调用fn函数,可见i由于是全局变量,是可以调用的。
def fn():
print(i)
fn()
1
其实,不在外部申明一个变量是否为global,也会由于作用域的相对位置,进行规定。如下,尽管j=1是在外部随意命名的,但是相对fn1来说,j=1是一个外部,进而是可以引用的。
j=1
def fn1():
print(j)
fn1()
1
当然了,需要在一个函数内部同时使用外部的全局变量和内部的局部变量,且使用同一个名字时,需要在调用全局变量的时候,申明该变量为global:
global i
i = 1
def fn():
global i
print(i)
i = 8
print(i)
fn()
1
8
我们再举个例子说明nonlocal的情况,在以下例子中,第一个程序未进行nonlocal num 关键字声明,进而程序在嵌套的函数中输出了该嵌套(enclosing)函数作用域的变量输出,而在该嵌套函数之外的外部函数中,num变量的值还是不变的。而当在嵌套函数内部声明了num变量为nonlocal,则该变量,num,不管在嵌套函数里,还是外部的函数,其值都是基于嵌套函数的声明的。
def outer():
num = 10
def inner():
# 未进行 nonlocal num 关键字声明
num = 100
print(num)
inner()
print(num)
outer()
100
10
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
100
100



