Python命名空间和作用域浅析
Python3 命名空间和作用域
命名空间
先看看官方文档的一段话:
A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。
命名空间(Namespace)是从名称到对象的映射,像是一个dict
,key
是变量的名字,value
是变量的值。大部分的命名空间都是通过 Python 字典来实现的
(__dict__)。
命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
一般有三种命名空间:
- 内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
- 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
命名空间查找顺序:
假设我们要使用变量 runoob,则 Python 的查找顺序为:局部的命名空间去 -> 全局命名空间 -> 内置命名空间。
如果找不到变量 runoob,它将放弃查找并引发一个 NameError 异常:
NameError: name 'runoob' is not defined。
对于闭包来说,这里有一点区别,如果在local namespace中找不到变量的话,还会去父函数的local namespace中找变量。
命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
我们可以使用locals()
globals() __dict__访问命名空间【locals()和globals()有一个区别是,locals只读,globals可以写】
import sys
m =sys.modules[__name__] #sys.modules获取了当前运行的模块
def f(x=1,y=2):
m=3
print(locals()) #获取当前的命名空间(函数体内即为函数内部的命名空间)
return 0
f()
print(m.__dict__) #__dict__成员获取了当前模块的命名空间
print(locals()) #获取当前的命名空间(全局)
print(globals()) #获取全局的命名空间
打印结果
{'x': 1, 'y': 2, 'm': 3}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001A2B0FA4188>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/flask-vuejs-madblog-master/study/100day/1.py', '__cached__': None, 'time': <module 'time' (built-in)>, 'datetime': <module 'datetime' from 'D:\\DeviceTestTools\\python\\lib\\datetime.py'>, 'sys': <module 'sys' (built-in)>, 'm': <module '__main__' from 'D:/flask-vuejs-madblog-master/study/100day/1.py'>, 'f': <function f at 0x000001A2B1074168>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001A2B0FA4188>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/flask-vuejs-madblog-master/study/100day/1.py', '__cached__': None, 'time': <module 'time' (built-in)>, 'datetime': <module 'datetime' from 'D:\\DeviceTestTools\\python\\lib\\datetime.py'>, 'sys': <module 'sys' (built-in)>, 'm': <module '__main__' from 'D:/flask-vuejs-madblog-master/study/100day/1.py'>, 'f': <function f at 0x000001A2B1074168>}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001A2B0FA4188>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/flask-vuejs-madblog-master/study/100day/1.py', '__cached__': None, 'time': <module 'time' (built-in)>, 'datetime': <module 'datetime' from 'D:\\DeviceTestTools\\python\\lib\\datetime.py'>, 'sys': <module 'sys' (built-in)>, 'm': <module '__main__' from 'D:/flask-vuejs-madblog-master/study/100day/1.py'>, 'f': <function f at 0x000001A2B1074168>}
【解释】:遇到一个名字的时候,Python解释器首先会去本地命名空间(Local)中查找,然后再去其所在函数的作用域(Enclosing Function)中查找,如果还没找到,就去全局命名空间(Global)中查找,最后会去__builtin__这个模块中查找,__builtin__模块在 Python3 中已经重命名成了builtins
【附】
from module import 和 import module
使用import module时,module本身被引入,但是保存它原有的命名空间,所以我们需要使用module.name这种方式访问它的 函数和变量。
from module import这种方式,是将其它模块的函数或者变量引到当前的命名空间中,所以就不需要使用module.name这种方式访问其它的模块的方法了。
if __name__ trick
python中的module也是对象,所有的modules都有一个内置的属性__name__,模块的__name__属性的值取决于如何使用这个模块,如果import module,那么__name__属性的值是模块的名字。如果直接执行这个模块的话,那么__name__属性的值就是默认值__main__。
module的一些内置属性
__name__: 上面已经介绍过
__file__ : 当前module的绝对路径
__dict__:
__doc__ :
__package__:
__path__:
作用域
A scope is a textual region of a Python program where a namespace is directly accessible. "Directly accessible" here means that an unqualified reference to a name attempts to find the name in the namespace.
作用域就是一个 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 –>gt; B。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
实战分析:
x = 1234
def test():
print(x)
x = 'abc'
test()
在上面的函数中,我们要打印一个名字x
,它首先会去本地命名空间中查找,没有找到。然后去当前函数test
的作用域中查找,找到了。此时Python解释器就会发现x
这个名字还没有添加到local本地命名空间中,就被引用了。所以就会抛出一个异常,说x
还未被赋值就被引用了。
import dis
x = 1234
def test_right():
print(x)
dis.dis(test_right)
print('-' * 20)
x = 1234
def test_error():
print(x)
x = 'abc'
dis.dis(test_error)
5 0 LOAD_GLOBAL 0 (print)
3 LOAD_GLOBAL 1 (x)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
--------------------
13 0 LOAD_GLOBAL 0 (print)
3 LOAD_FAST 0 (x)
6 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
9 POP_TOP
14 10 LOAD_CONST 1 ('abc')
13 STORE_FAST 0 (x)
16 LOAD_CONST 0 (None)
19 RETURN_VALUE
通过查看这两个函数的反汇编出来的代码可以看到,这两个函数访问x的时候,一个是通过LOAD_GLOBAL
指令,访问的全局命名空间,另外一个则是通过LOAD_FAST
指令访问的本地命名空间。
- 点赞
- 收藏
- 关注作者
评论(0)