命名空间(Namespace)

命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。

命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。

分类

一般有三种命名空间:

  • 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
  • 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  • 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)

查找顺序:

假设我们要使用变量 runoob,则 Python 的查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间

如果找不到变量,它将放弃查找并引发一个 NameError 异常:

生命周期:

命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var=0
print('所有函数外部,变量var的值是:\n',var)
def out_fun():
# 在out_fun内部,in_fun外部定义变量
var=1
print("out_fun内部,in_fun外部,变量var的值是:\n",var)
# 内部函数
def in_fun():
var=2
print("in_fun内部,变量var的值是:\n", var)
# 内部函数实现
in_fun()

print(var)

# 外部函数实现
out_fun()
# 依次输出0,1和 2

作用域(Scope)

作用域就是一个 Python 程序可以直接访问命名空间的正文区域。

在一个 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

1
2
3
4
5
g_count = 0  # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域

Python 中只有模块(module),类(class)以及函数(deflambda)才会引入新的作用域,其它的代码块(如if/elif/elsetry/exceptfor/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:

1
2
3
4
5
6
>>> if True:
... msg = 'I am from Runoob'
...
>>> msg
'I am from Runoob'
>>>

实例中 msg 变量定义在 if 语句块中,但外部还是可以访问的。

如果将 msg 定义在函数中,则它就是局部变量,外部不能访问:

1
2
3
4
5
6
7
8
>>> def test():
... msg_inner = 'I am from Runoob'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
>>>

全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

global和nonlocal关键字

为什么要用global和nonlocal关键字

当内部作用域想修改外部作用域的变量时,会出现下面的报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 定义出错
def out_fun():
# 在out_fun内部,in_fun外部定义变量
var=0
print("内部函数执行【前】,变量var的值是:\n",var)

# 内部函数
def in_fun():
var+=1
print("in_fun内部,变量var的值是:\n", var)
# 内部函数实现
in_fun()
print("内部函数执行【后】,变量var的值是:\n",var)

# 外部函数实现
out_fun()
# 会报错

报错信息如下:

1
2
3
UnboundLocalError: cannot access local variable 'var' where it is not associated with a value
内部函数执行【前】,变量var的值是:
0

出错信息在var+=1这个语句中。

在Python 3中,如果在内部函数中赋值给一个变量,Python默认这个变量是局部变量,除非你明确地声明它是非局部变量(nonlocal)或者全局变量(global)。因此,当你在 in_fun 中执行 var += 1 时,Python会认为你是在尝试定义一个局部变量 var,但是你又没有在 in_fun 的函数体中为其赋初值,只是尝试增加它的值,这就导致了错误。

这不同于直接定义var=2,因为var=2是直接重新定义,Python会认为var是一个新的、in_fun内部的局部变量。但是如果写成var+=1,Python会寻找var等于几,但是找不到,因为没有声明过。

为了解决这个问题,可以用nonlocal关键字。

nonlocal关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# nonlocal
def out_fun():
# 在out_fun内部,in_fun外部定义变量
var=0
print("内部函数执行【前】,变量var的值是:\n",var)
# 内部函数
def in_fun():
# 告诉Python var是外部函数的局部变量
nonlocal var
var+=1
print("使用了nonlocal关键字声明后,in_fun内部,变量var的值是:\n", var)
# 输出1
# 内部函数实现
in_fun()
print("内部函数执行【后】,变量var的值是:\n",var) # var此时为1,因为内部函数修改过了
# 输出0

# 外部函数实现
out_fun()
# 依次输出0,1,1

但是,在out_fun()外部使用下列语句会出错:

1
2
# 下面的print语句会报错
print(var)

因为var是定义在out_fun内的局部变量,虽然在in_fun中声明,但是并不能在外部访问。

global 关键字

除此之外,我们还可以用global关键字,实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# global关键字
var_a=1
var_b=2
def out_fun():
# 访问全局变量需要声明
global var_a
print("外部函数访问全局变量var_a,结果是:",var_a)
var_a+=1
print("外部函数修改全局变量var_a,结果是:",var_a)
def in_fun():
# 访问全局变量需要声明
global var_b
print("内部函数访问全局变量var_b,结果是:", var_b)
var_b += 1
print("内部函数修改全局变量var_b,结果是:", var_b)

in_fun()

out_fun()
# 依次输出1,2,2,3

值得注意的是,如果我们直接在out_fun 内部,in_fun外部定义了var_b(而不是在out_fun函数外部),此时在in_fun()函数里面声明global var_b,会出错,因为global声明的变量类型必须是在函数外面的全局变量。

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def out_fun():
var_b = 2
# 访问全局变量需要声明
def in_fun():
# 访问全局变量需要声明
global var_b
print("内部函数访问全局变量var_b,结果是:", var_b)
var_b += 1
print("内部函数修改全局变量var_b,结果是:", var_b)

in_fun()

out_fun()
# 依次输出1,2,2,3

报错如下:

1
2
3
4
  File "E:\codes\Python\python_relearning\namespace.py", line 84, in in_fun
print("内部函数访问全局变量var_b,结果是:", var_b)
^^^^^
NameError: name 'var_b' is not defined. Did you mean: 'vars'?

参考文章