1. 命名空间
Python程序运行时, 会根据需要创建命名空间. 按照命名空间的生命周期来划分, 可将命名空间分为4种
- 内置 ( Built-in )
- 全局 ( global )
- 闭包 ( Enclosing )
- 本地 ( local )
1.1 内置命名空间
内置空间中保存着Python内置的变量对象(如 print函数, list对象等 ), 内置命名空间由Python解释器创建和销毁, 只要python程序在运行, 内置空间的变量就是生效的. 如:任何时候都可以调用print函数和使用list创建列表.
内置空间的所有变量保存在变量__builtins__中, 可以使用 dir(__builtins__) 来查看其中的内容
In [1]: dir(__builtins__)
Out[1]:
['ArithmeticError',
'AssertionError',
'AttributeError',
...
'__name__',
'__package__',
...
'print',
'property',
'range']
可以看到其中保存了内置的函数和数据类型(print, range, tuple), 内置变量(__name__, __doc__), 和通用的异常(TypeError).
1.2 全局命名空间
全局命名空间包含在主程序中定义的任何名称。Python 在主程序体启动时创建全局命名空间,并且它一直存在直到解释器终止。包含在py文件中定义的和从其他模块import的内容.
举一个例子来更好的理解这个概念, 创建一个namespace.py 文件
from random import randint
VAR = 'global varible'
class Demo:
def __init__(self):
pass
def func():
print('function')
print(globals())
执行python namespace.py 执行print(globals())语句会将当前全局变量打印出来. 结果如下
{'__name__': '__main__',
'__doc__': None,
'__package__': None,
'__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000024E79813970>,
'__spec__': None,
'__annotations__': {},
'__builtins__': <module 'builtins' (built-in)>,
'__file__': 'c:\\Users\\Documents\\demo\\namespace.py',
'__cached__': None,
'randint': <bound method Random.randint of <random.Random object at 0x0000024E79A4FF40>>,
'VAR': 'global varible',
'Demo': <class '__main__.Demo'>,
'func': <function func at 0x0000024E79817F70>}
可以看到, 定义的变量VAR, 函数func和类Demo, 以及从random模块import的函数randint被加入到了golbal命名空间中.
1.3 本地和闭包命名空间
每当一个函数被执行时,解释器都会创建一个新的命名空间。该命名空间就是函数的本地命名空间,该命名空间函数返回后被销毁。
如果在函数中再次定义函数, 对于外层函数的本地命名空间就是内层函数的闭包命名空间.
下面通过一个例子来说明
定义一个函数 outter, 接受一个参数 arg, 函数中定义一个变量var和一个函数 inner. inner函数接受一个参数 a, 函数中定义一个变量var.
In [1]: def outter(arg):
...: var = 'enclosing'
...: def inner(a):
...: var = 'inner var'
...: print('inner locals: ', locals())
...: inner('inner var')
...: print('outter locals: ', locals())
...:
In [2]: outter('param')
inner locals: {'a': 'inner var', 'var': 'inner var'}
outter locals: {'arg': 'param', 'var': 'enclosing', 'inner': <function outter.<locals>.inner at 0x00000202B37C8558>}
执行之后可以看到在inner函数中, 本地命名空间locals中包含了变量a 和var. 在outter函数中, 本地命名空间locals中包含变量arg, var 和函数inner. outter函数中的本地命名空间即是inner函数的闭包命名空间.
2. 变量作用域
多个命名空间的结果是, 同一个变量名称可以在多个命名空间中存在, 指向不同的对象, 彼此之间相互不干扰. 当同一变量在不同的命名空间中存在时, Python遵循如下Local > Enclosing > Global > Built-in 的顺序搜索, 如果最终还是没有找到对应的对象, 抛出NameError异常
下面使用几个实例来展示一下变量的搜索过程, 变量 var 在函数inner, 函数outter和全局作用域中都有定义, 执行函数outter()来运行其中的inner函数后, print语句先在本地作用域中查找var引用的对象, print 出来是 inner var
在函数inner中没有定义 var 的时候,降级到inner函数的闭包作用域 (即outter函数的本地作用域 )去寻找, print 出来的结果是 enclosing
在函数outter中也没有定义 var 的时候,降级到全局作用域去寻找, print 出来的是全局变量 global
3. 修改外部命名空间的变量
3.1 可变对象 如:list, dict
对于可变对象, 在内部作用域中对外部对象进行引用, 对对象的修改会作用到对象本身.
In [1]: array = ['adobe', 'abandon', 'aladdin']
In [2]: def modify():
...: array.append('ariza')
In [3]: modify()
In [4]: array
Out[4]: ['adobe', 'abandon', 'aladdin', 'ariza']
3.2 不可变对象 如:int, string
正常情况下, 函数修改不了作用域外的不可变对象.
In [1]: var = 100
In [2]: def func():
...: var = 200
...: print(var)
In [3]: func()
200
In [4]: var
Out[4]: 100
3.3 使用global 和nonlocal
需要修改作用域外的变量, 可以使用 global 声明变量是全局变量. 使用global将var声明为全局变量后, 在函数内func内对var的修改, 将会修改全局变量var的值.
In [1]: var = 100
In [2]: def func():
...: global var
...: var = 200
...: print(var)
In [3]: func()
200
In [4]: var
Out[4]: 200
nonlocal 的使用方式同理, 区别nonloca将变量声明为修改闭包作用域中的变量.
In [1]: def outter():
...: var = 'enclosing'
...: def inner():
...: nonlocal var
...: var = 'inner var'
...: inner()
...: print(var)
In [2]: outter()
inner var
如上代码可见, inner函数中将var声明为nonlocal, 运行inner()函数之后, outter函数中的var被修改成 'inner var'
可以理解为 nonlocal 和 global 将当前作用域外的变量, 拿到了本地作用域使用.
4. 总结
- Python的 4 种命名空间, 以及作用域的优先级Local > Enclosing > Global > Built-in
- 修改作用域外的数据, 可变对象的修改直接影响本地命名空间外的数据
- 修改作用域外的数据, 不可变对象, 使用nonlocal和global声明之后才可以修改作本地命名空间的数据