# 5.3 命名空间

变量作用域是变量起作用的范围。在程序执行过程中，如果解释器执行的代码在某变量作用域之内，那么这些代码就会受到这个变量的影响，反之，则不会。我们通过上一节的学习，已经掌握了函数的用法，使用函数进行编程与我们之前所学的编程技巧最大的不同就是函数编程涉及到了变量作用域的问题。我们对变量进行不同的声明方式，可以改变变量的作用域，对变量作用域进行巧妙运用，可以事半功倍。

### 5.2.1 命名空间

**命名空间**(namespace)是一种被多种编程语言使用的代码语言组织形式。命名空间的主要作用是用来**组织和重用代码**。命名空间也被称为**作用域(Scope)。**

在实际开发中，因为不同人员在程序中创建的变量存在一定的重复，当把这些程序文件写入库中就会出现重复的变量和函数，当解释器（Python）或编译器（C++、Java）在使用这些变量或函数时，就会出现错误。命名空间将这些不同开发人员创建的变量分隔开来，保证在这个命名空间中，每个变量和函数的名称是唯一的。具体到每个函数上，即每一个函数拥有唯一的命名空间。这样在使用这些函数或变量时，就不会被这种错误困扰。

Python定义的三种命名空间：局部（local）命名空间，全局命名（global）空间和内建（built-in）命名空间。

当变量引用数据对象时，python解释器会依次在以下四种作用域（命名空间）内进行查找：

1. 局部作用域
2. 外层函数定义下的局部作用域（如果1.中局部作用域包含于多层嵌套函数，则一层一层向外查找）
3. 全局作用域
4. 内置作用域（builtins模块）

如果解释器进行了以上四步，都没有找到待查找的变量，这时候解释器就会报错。

我们将在接下来几节中，对这些命名空间进行深入讨论。

### 5.2.2 局部作用域

**代码块(Code Block)是一组由代码构成的功能“单元”，如函数，类，模块。一个代码块通常可以单独运行。我们在5.2.1节所列举的def案例，都可以称之为代码块**。

\*\*局部变量(Local Scope)\*\*是在函数内定义的只在该函数中起作用的变量，且与函数外创建的具有相同名称的变量无关。**局部作用域（Local Scope）函数定义下的代码块，也被称为局部函数作用域（Local Function Scope）**。

有几点需要注意的是：

* 在函数内创建的变量只能在函数内使用，而不能在函数外使用。
* 在函数内创建的变量，其名称可以与在函数外创建的变量的名称相同。当函数内变量名称与函数外的变量名称相同时，函数内外的变量引用的对象仍然是不同的。
* 函数内创建的变量，它对于该函数而言是**局部的**。
* 函数外创建的变量，它对于该函数而言是**非局部的**，而对于整个文件来说是全局的。
* 每次对函数进行调用，都会重新创建一个新的局部作用域。每次函数调用结束，其创建的局部作用域都会被清除。

  例:

\>>>x=0 #函数外创建的变量

\>>>def print\_x():

x=1 #局部变量

print(x)

\>>>print\_x() #局部变量的

1

\>>>x #函数外变量值保持不变

0

### 5.2.3 全局作用域

\*\*全局变量(Global Scpoe)\*\*是在所有函数以外创建的变量，它可以在整个文件中任意位置使用。\*\*全局作用域（Global Scope）\*\*是整个文件（模块），也被称为全局模块定义域。

如果全局变量的值在函数内部被重新赋值，那么这个全局变量所引用的数据对象的值会随之变化，这种变化将体现在整个文件。相似地，如果全局变量在外部赋值，那么赋值效果也同样反映在函数内部。

我们在上一节中的例子中，在函数外创建的变量就是全局变量。对于模块而言，导入模块后，模块内部属性成为全局变量。可以直接使用。

可以使用global来定义全局变量。

global语句的使用方式如下：

global variable\_name1, variabel\_name2, …

具体来说，global语句的作用有两种：

1. 在函数内为已经在函数外部创建的变量重新赋值.
2. 在函数内部声明一个全局变量。

现在让我们来看这样一个例子：

\>>>x=0 #全局变量

\>>>def assigment\_x():

print(x)

\>>>assigment\_x()

在上例中，我们先创建了一个全局变量x，然后声明了一个打印变量x的函数。当我们调用这个函数时，因为变量x不存在于局部作用域，因此，python解释器会在全局作用域中查找此变量。最终将全局变量x的值打印出来。

### 5.2.4 内置作用域

#### 内置名称

在开始学习内置作用域前，首先让我们学习一个python函数dir().

dir函数的使用方法如下：

dir(\[object])

当不向dir函数内传入对象类型的参数时，dir函数以列表形式返回当前范围内的变量、方法和定义的对象类型。当向dir函数传入参数时，该函数会以列表形式返回此参数的属性、方法。如果参数包含方法\_\_dir\_*()，该方法将被调用。如果参数不包含\_\_dir\_*()，该方法将最大限度地收集参数信息。

怎么用？

在Python中任何对象都是对象，一种数据类型，一个模块，都要自己的属性和方法。我们可以使用dir()函数来查看对象内的所有属性和方法。除部分常用方法外，其他都可以用dir()来实现。

以字符串类型为例，在命令窗口中输入dir(str)或者dir(‘’)即可查看字符串的类型；在（）中传入一个空列表对象\[]或者一个列表数据类型的变量名即可查看列表中的方法：

\>>> dir(\[])

\['**add**', '**class**', '**contains**', '*\_delattr\_*', '*\_delitem\_*', '*\_dir\_*', '**doc**', '**eq**', '**format**', '*\_ge\_*', '*\_getattribute\_*', '*\_getitem\_*', '*\_gt\_*', '**hash**', '*\_iadd\_*', '*\_imul\_*', '*\_init\_*', '*\_init\_subclass\_*', '*\_iter\_*', '**le**', '*\_len\_*', '*\_lt\_*', '*\_mul\_*', '\_\_ne\_\_', '**new**', '**reduce**', '*\_reduce\_ex\_*', '*\_repr\_*', '**reversed**', '*\_rmul\_*', '*\_setattr\_*', '*\_setitem\_*', '*\_sizeof\_*', '**str**', '*\_subclasshook\_*', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

或者

\>>> x=\['a','b']

\>>> dir(x)

\['**add**', '**class**', '**contains**', '*\_delattr\_*', '*\_delitem\_*', '*\_dir\_*', '**doc**', '**eq**', '**format**', '*\_ge\_*', '*\_getattribute\_*', '*\_getitem\_*', '*\_gt\_*', '**hash**', '*\_iadd\_*', '*\_imul\_*', '*\_init\_\_', '*\_init\_subclass\_*', '*\_iter\_*', '**le**', '*\_len\_*', '*\_lt\_*', '*\_mul\_*', '**ne**', '**new**', '**reduce**', '*\_reduce\_ex\_*', '*\_repr\_*', '**reversed**', '*\_rmul\_*', '*\_setattr\_*', '*\_setitem\_*', '*\_sizeof\_*', '**str**', '*\_subclasshook\_\_', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

内置作用域是一个名为builtins的内置模块。使用内置作用域前需要导入该模块。

我们把定义在builtins模块中的对象、函数、保留字等统称为**内置名称**。我们使用dir函数这些**内置名称**。

\>>>import builtins

\>>>dir(builtins)

\['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', …'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']

通过builtins的输出结果，我们可以发现，builtins模块包含了异常、 对象类型和函数等。并且这些异常、对象、函数有些已经被我们熟知，例如AttributeError，pow函数。那么这些异常、对象或函数与我们日常直接使用的有什么区别呢？实际上，无论是直接使用也好，还是使用builtins中的也好，这两种方式是等价的。也就是说，当我们使用内置变量、函数时，有两种方式：第一种是直接使用，这种情况下，Python解释器会对作用域进行逐级查找。在内置函数没有被重新定义的情况下，Python最终会检查内置作用域，此时会调用内置函数。第二种情况下，是手动导入builtins模块，然后使用其中的函数。这两种方式使用的函数是等价的。

\>>> print

\<built-in function print>

\>>>import builtins

\>>>builtins.print

\<built-in function print>

print is builtins.print

True

#### 内置名称重定义

当我们使用变量或函数时，Python解释器会逐级按以下顺序依次查找作用域：全局作用域、局部作用域和内置作用域，并且总会使用其首次定义。也就意味着，Python解释器总是偏爱更高级别的作用域。对于函数来说，当全局作用域、局部作用域和内置作用域同时定义有相同名称的函数时，会优先使用全局作用域中定义的函数。基于这一点，我们可以对内置名称进行重定义。

举一个不恰当的例子，我们希望pow函数执行平方运算，那么就可以对pow进行重定义：

\>>>pow(2,4)

16

\>>> def pow(x):

return x\*x

\>>> pow(2)

4

然而，对内置名称进行重定义往往会造成问题。而且，Python解释器并不会对内置名称重定义进行警告。如果不小心在交互式命令行里对内置名称进行了重命名，需要恢复，那么有两种方法完成这个目标：1. 重启命令行 2. 使用del语句对重定义名称进行移除。

例：del移除重定义内置函数

\>>> pow(2, 4)

Traceback (most recent call last):

File "\<pyshell#25>", line 1, in \<module>

pow(2, 4)

TypeError: pow() takes 1 positional argument but 2 were given

\>>> del pow

\>>> pow(2, 4)

16

### 5.2.5 嵌套作用域

嵌套是一种代码写法，直观上可以理解为，当作用域中包含一层或多层作用域时，就构成了嵌套。

在一个多层嵌套函数中，若要查找一个变量，首先会查找该变量所在的局部作用域。如果存在此变量，则返回此变量，若不存在此变量，则会查找此作用域外的作用域中包含的变量，重复此过程直到查找到全局作用域，如果全局作用域中仍无此变量，则会查找内置作用域，如果仍不存在此变量，则会报错。

在一个多层嵌套函数中，若要对一个变量赋值，首先会查找该变量所在的局部作用域，若存在此变量，则对其赋值，存在此变量，则会查找此作用域外的作用域中包含的变量，重复此过程直到查找到全局作用域，如果全局作用域中仍无此变量，则会查找内置作用域，如果仍不存在此变量，则会在原作用域中创建一个新的变量。

例

x=0

def print\_x(): #定义三层嵌套函数

print(x) #嵌套函数第一层向外层全局作用域查找变量x，得变量x值为0

def print\_x(): #第二层嵌套函数

x=1

def print\_x(): #第三层嵌套函数

print(x) #打印x的取值，三层嵌套函数向外层函数查找，得变量x值为1

print\_x()#调用第三层嵌套函数

print\_x() #调用第二层嵌套函数

print\_x() #调用三层嵌套函数

在此例中，内层作用域中定义的变量和函数覆盖了外层同名变量和函数。
