Python命名空间和作用域浅析

举报
子都爱学习 发表于 2021/04/03 14:32:39 2021/04/03
【摘要】 Python3 命名空间和作用域命名空间先看看官方文档的一段话:A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。命名空间(Namespace)是从名称到对象的映射,像是一个dict,key是变量的名字,value是变量...

Python3 命名空间和作用域

命名空间

先看看官方文档的一段话:

A namespace is a mapping from names to objects.Most namespaces are currently implemented as Python dictionaries。

命名空间(Namespace)是从名称到对象的映射,像是一个dictkey是变量的名字,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指令访问的本地命名空间。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。