Python 名称空间与作用域

这篇文章介绍 Python 的名称空间以及变量的作用域。

Python 的名称

Python 的名称(Name)是对象的一个标识(Identifier)。我们知道,在 Python 里面一切皆对象,名称就是用来引用对象的。说得有点玄乎,我们以例子说明。

例如,在a = 2这个语句中,2是个存储在内存中的一个对象,名称a则会引用2这个对象,“引用”的含义是指可以通过名称a来使用2这个对象。我们可以使用id()函数来获取对象的地址。

1
2
3
a = 2
print(id(2))
print(id(a))

输出:

43547988
43547988

可以看到,两都均指向同一个对象。我们再来看复杂一点的代码。

1
2
3
4
5
6
7
8
a = 2
print 'id(a) = %s' % id(a)
a = a + 1
print 'id(a) = %s' % id(a)
print 'id(3) = %s' % id(3)
b = 2
print 'id(2) = %s' % id(2)
print 'id(b) = %s' % id(b)

输出:

id(a) = 55606612
id(a) = 55606600
id(3) = 55606600
id(2) = 55606612
id(b) = 55606612

上述的代码发生了什么呢?我们以图1来说明。


图1:Python 的名称与对象

起初,名称a引用对象2
然后,执行操作a = a + 1,这时对象3被创建,名称a引用对象3,所以id(a)id(3)输出相同的地址;
最后,执行b = 2,名称b引用对象2,所以id(2)id(b)输出相同的地址

这个例子也展示了 Python 在执行变量的赋值时,并不会重复创建一个对象的事实。名称采用动态绑定的机制使得 Python 更加高效,同一个名称可以引用不同类型的对象。

1
2
3
a = 5
a = 'hello world'
a = [1, 2, 3]

可以看到,a先后引用了数字,字符串,列表的类型的对象,这在 Python 中完全是合法的。

Python 的名称空间

了解 Python 的名称后,我们来看 Python 的名称空间。

名称空间是名称到对象的映射。

在 Python 中,名称空间采用字典来实现。Python 的名称空间包括:

  • 内置名称空间,例如,内置名称空间包含 Python 的内置函数,如,abs()
  • 模块名称空间,全局名称空间,在模块内定义的名称
  • 局部名称空间,例如,在函数(function)或者类(class)被调用时,其内部包含的名称

不同的名称空间内的名称不会相互冲突,即是它们采用相同的名称。这也正是名称空间的作用。

内置名称空间在 Python 解释器启动时就创建了,直到 Python 解释器退出时内置名称空间才失效。这使得我们可以在程序的任何位置使用内置名称空间内的名称,例如,id()print()等函数。
模块名称空间当模块被引用时创建,直到 Python 解释器退出时模块名称空间才失效。
函数名称空间在函数被调用时创建,函数返回后失效。


图2:Python 的名称空间

Python 变量的作用域

Python 的作用域(scope)决定了我们在程序中能否直接使用名称空间中的名称,直接访问的意思是指不需要在名称前添加名称空间的前缀。对于 Python 来说,至少存在以下三类的作用域。

  • 函数作用域,包括了函数内的局部名称
  • 模块作用域,包括了模块内的全局名称
  • 内置作用域,包括了内置名称

当在函数内部使用一个名称时,为了查找出该名称所引用的对象,Python 解释器先在函数名称空间查找,接着在模块名称空间查找,最后在内置名称空间查找,直到寻找到该名称为止。
当函数 A 处于函数 B 的内部时,函数 A 的作用域处于函数 B 作用域之内。

例子

1
2
3
4
5
6
def outer_function():
b = 20
def inner_func():
c = 30
a = 10

在这个例子中,名称a在全局名称空间中,名称b在函数outer_function的局部名称空间,名称c则在函数inner_func的局部名称空间。
当我们在函数inner_func时,c是个局部的名称,b是个非局部的名称,而a则是个全局的名称。在函数inner_func中,我们可以对c进行读取操作和赋值操作,而只能对ba进行读取操作。当对b进行赋值时,一个新的名称将会被创建,这个新的名称处于inner_func函数局部名称空间中。对a进行赋值时也会在局部名称空间中创建一个新的名称。

我们使用以下的例子来说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def outer_function():
a = 20
def inner_function():
a = 30
print('a = %s' % a)
inner_function()
print('a = %s' % a)
a = 10
outer_function()
print('a = %s' % a)

输出:

a = 30
a = 20
a = 10

在函数inner_function中,我们对a进行了赋值操作,但函数outer_function中的a仍然为20,全局名称空间中的a则仍然为10

为了在函数作用域中对全局的名称进行读取或者赋值操作,需要将这个名称声明为global

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def outer_function():
global a
a = 20
def inner_function():
global a
a = 30
print('a = %s' % a)
inner_function()
print('a = %s' % a)
a = 10
outer_function()
print('a = %s' % a)

输出:

a = 30
a = 30
a = 30

可以看到,通过global,我们在不同的作用域对全局名称a进行了赋值操作,最后在函数inner_function中对a的赋值也就是全局名称a的值。

参考资料

  1. https://www.programiz.com/python-programming/namespace
  2. https://docs.python.org/3/tutorial/classes.html
  3. https://www.python-course.eu/namespaces.php
  4. https://www.python-course.eu/global_vs_local_variables.php
  5. https://www.programiz.com/python-programming/modules
  6. https://code.tutsplus.com/tutorials/what-are-python-namespaces-and-why-are-they-needed--cms-28598
  7. https://bytebaker.com/2008/07/30/python-namespaces/
  8. https://stackoverflow.com/questions/3190706/nonlocal-keyword-in-python-2-x