python中的作用域与命名空间

python中的几个命名空间:
1.局部命名空间:包含局部变量
2.全局命名空间: 当前模块的最外层的全局变量
3.内置(built-in)命名空间: 包含了内置的变量/关键字等

这三个命名空间分别有各自的生命周期:
1.局部命名空间:函数的局部命名空间,在函数调用时创建,函数返回或者由未捕获的异常时销毁;类定义的命名空间,在解释器读到类定义创建,类定义结束后销毁
2.全局命名空间:模块的全局命名空间在模块定义被解释器读入时创建,解释器退出时销毁
3.内置命名空间:在Python解释器启动时创建,解释器退出时销毁

python代码中查找一个变量按照LEGB的顺序查找:
1.Local:首先搜索,包含局部名字的最内层(innermost)作用域,如函数/方法/类的内部局部作用域;
2.Enclosing:根据嵌套层次从内到外搜索,包含非局部(nonlocal)非全局(nonglobal)名字的任意封闭函数的作用域。如两个嵌套的函数,内层函数的作用域是局部作用域,外层函数作用域就是内层函数的 Enclosing作用域;
3.Global 倒数第二次被搜索,包含当前模块全局名字的作用域;
4.Built-in 最后被搜索,包含内建名字的最外层作用域。

>>> i=2
>>> def func():
...    print(i)
... 
>>> func()
2

注:程序运行过程中LGB一定存在,E不一定存在

>>> i=2
>>> print(i)
2

这种类型的程序,命令空间的说明如下:
<b>Usually, the local scope references the local names of the (textually) current function. Outside functions, the local scope references the same namespace as the global scope: the module's namespace. Class definitions place yet another namespace in the local scope.</b>

还有一个地方需要注意的是;

>>> i=2
>>> def fun():
...     print(i)
...     i=3
>>> fun()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fun
UnboundLocalError: local variable 'i' referenced before assignment

如果内部函数有引用外部函数的同名变量或者全局变量,并且对这个变量有修改.那么python会认为它是一个局部变量,所以在print(i)时该变量还没有定义,出现UnboundLocalError。
那么如果需要修改global中的变量怎么办呢?可以使用global关键字:

>>> i=2
>>> def func():
...   global i
...   print(i)
...   i=3
... 
>>> func()
2
>>> print(i)
3

在python3.X版本中引入了关键字nonlocal,用来在函数或其他作用域中使用外层(非全局)变量
(The nonlocal
statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope。)

i=2
def func():
    i=3
    def func1():
        i=4
        nonlocal i
        print(i)
        i+=1
    return func1
a=func()
a()
print(i)
a()

---outputs---
4
2
4
/usercode/file3.py:7: SyntaxWarning: name 'i' is assigned to before nonlocal declaration
  nonlocal i
#!/usr/bin/python
i=2
def func():
    i=3
    def func1():
        nonlocal i
        print(i)
        i+=1
    return func1
a=func()
a()
print(i)
a()

---outputs---
3
2
4

python中内置了locals和globals2个方法可以查询局部和全局的命名空间

#!/usr/bin/python
i=2
def func():
    j=4
    print("locals:",locals())
print("globals:",globals())
func()

---outputs---
globals: {'__loader__': <_frozen_importlib.SourceFileLoader object at 0x7fb1ce70f0b8>, 
'__spec__': None, '__name__': '__main__', '__cached__': None,
'__builtins__': <module 'builtins' (built-in)>, 
'func': <function func at 0x7fb1ce737b70>, 'i': 2, '__file__': '/usercode/file3.py', '__package__': None, '__doc__': None}
locals: {'j': 4}

locals和globals有个重要的区别:

#!/usr/bin/python
def func1(i, info):
    x = 12345
    print(locals())
    locals()["x"]= 6789
    print("x=",x)
 
y=54321
func1(1 , "first")
globals()["y"]= 9876
print( "y=",y)

---outputs---
{'x': 12345, 'i': 1, 'info': 'first'}
x= 12345
y= 9876

这是因为(待研究为什么这么设计):
1.locals 实际上没有返回局部名字空间,它返回的是一个拷贝。所以对它进行改变对局部名字空间中的变量值并无影响。
2.globals 返回实际的全局名字空间,而不是一个拷贝。所以对 globals 所返回的 dictionary 的任何的改动都会直接影响到全局变量

此外,我们import时有2种方式:
1.from module import xxx
2.import module
在使用第一种时,会把被导入的module种方法和属性放到当前的命名空间中,所以可以直接使用方法和属性进行访问;而在第二种中,模块被导入,保留了自身的命名空间,所以访问其中的方法需要使用module.method()

参考:

  1. Python进阶_关于命名空间与作用域(详解)
  2. Python命名空间和作用域窥探
  3. Python Scopes and Namespaces
  4. 命名空间的本质

推荐阅读更多精彩内容