您现在的位置是:主页 > news > 网站建设分金手指排名二八/新闻投稿
网站建设分金手指排名二八/新闻投稿
admin2025/5/9 0:10:12【news】
简介网站建设分金手指排名二八,新闻投稿,wordpress属于,网站做批发文具本文主要介绍 Python 的命名空间和作用域,以及 nonlocal 和 global 的用法。阅读本文预计需要 15 min。 一文了解 Python 中的命名空间和作用域1. 前言2. 命名空间3. 作用域4. 全局变量 VS 局部变量5. global VS nonlocal6. 小结7. 巨人的肩膀1. 前言 Python 命名空…
本文主要介绍 Python 的命名空间和作用域,以及 nonlocal 和 global 的用法。阅读本文预计需要 15 min。
一文了解 Python 中的命名空间和作用域
- 1. 前言
- 2. 命名空间
- 3. 作用域
- 4. 全局变量 VS 局部变量
- 5. `global` VS `nonlocal`
- 6. 小结
- 7. 巨人的肩膀
1. 前言
Python 命名空间(Namespace)
和作用域(Scope)
对于所有 Python 程序员都非常有用。弄明白 Python 命名空间和作用域对于我们 Python 编程和调试 Bug 都很有用!
为什么需要命名空间和作用域呢?从设计层面,我目前的理解是为了计算机正确执行代码。有了规范协议,按照规范协议写代码,开发人员和计算机就能互相理解对方,默契配合,代码也就能正确执行。把这些当做一个规范协议,符合规范的代码,才能正确执行,不然就是 Bug 了。
本文主要内容:
- 命名空间
- 作用域
- 全局变量 VS 局部变量
global
VSnonlocal
2. 命名空间
命名空间(Namespace)
:指的是从名字(Name)
到对象(Object)
的映射(Mapping
)。可以把它理解为一张“映射表”
。
这像我们初中学的映射关系,命名空间是名字和对象的一张映射表
。通过名字,我们就可以找到这个对象的位置。如同,名字是钥匙,对象是房间,一把钥匙打开一间房间。
命名空间是互相独立的。关于命名空间的重要一点是,不同命名空间中的名称之间绝对没有关系
,在不同的映射表中可以出现相同的名字,而不会出问题,例如,两个不同的模块都可以定义一个 maximize
函数而不会产生混淆,只要模块的用户在其前面加上模块名称。
Python 中常见的命名空间有:
存放内置函数的集合
(包含abs()
这样的函数,和内建的异常等),可以认为是”内置函数映射表“。模块中的全局名称
,可以认为是“模块映射表”。函数调用中的局部名称
,可以认为是“函数映射表”。
在不同时刻创建的命名空间拥有不同的生命周期。:
内置名称的命名空间
:是在 Python 解释器启动时创建的,永远不会被删除,直到退出 Python。如:Python 内置的关键字、函数等就放在这个里面。模块的全局命名空间
:在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出。被解释器的顶层调用执行的语句,从一个脚本文件读取或交互式地读取,被认为是__main__
模块调用的一部分,因此它们拥有自己的全局命名空间。如:我们定义的全局变量就放在模块的全局命名空间。(内置名称实际上也存在于一个模块中;这个模块称作builtins
。)函数的局部命名空间
:在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。(事实上,比起描述到底发生了什么,忘掉它更好。)当然,每次递归调用都会有它自己的局部命名空间。如:我们定义的局部变量就放在局部命名空间。
相信现在,大家对于命名空间有了基本的理解,简单说,它就是一个名字和对象的映射表。一个程序运行的时候,会生成很多个映射表(命名空间),那么这些命名空间有什么用呢?接下来就介绍作用域。
3. 作用域
作用域(Scope)
: 是一个命名空间(映射表)可直接访问的 Python 程序的文本区域。 这里的 “可直接访问” 意味着对名称的非限定引用会尝试在不同命名空间中查找名称。
这是官方文档给的解释。我的理解是:把源代码(程序)比作中国的话,命名空间(映射表)就是各级人民政府,作用域就好比各级人民政府管辖的区域(代码文本片段),即映射表管理的代码文本片段。如:一个函数的命名空间(映射表)管理的范围就是这个函数代码文本。
我们知道中央可以管理地方,地方不可以管理中央。命名空间(映射表)也是一样的,有些命名空间(映射表)可以管理整个代码,有些命名空间(映射表)只能管理一个函数代码。那么问题来了,这么多命名空间(映射表),Python 是怎么找到需要的对象的呢?
其实,跟政府管理时,一级一级向上报告一样,Python 也是一级一级的不同的命名空间(映射表)查找,如果所有的映射表都找不到,就报错。
查找的顺序就是之前说过的 LEGB
原则。
作用域是静态确定,动态使用
。 在 Python 程序运行的任何时间,至少有 3 个命名空间是能够直接访问的作用域:
-
首先搜索包含局部名称(局部变量)的最内部的作用域。这是最先查找的命名空间(映射表),可以当作村委,处在基层,最下面的位置。
(Local, scope L)
-
从最近的闭包作用域开始搜索的任何闭包函数的作用域,如:非局部名称(non-local 变量)、非全局名称(non-global)等。简单说就是存在函数嵌套时,一级一级往上层函数的命名空间查找。
Enclosing scope, E)
-
倒数第二个作用域是包含当前模块全局名称(全局变量)的命名空间(映射表)。如:全局变量。
(Global scope, G)
-
最后搜索最外面的作用域,即包含内置名称(内置函数、关键字等)的命名空间(映射表)。
(Builtin scope, B)
注意:类定义单独放在一个局部作用域的命名空间。
重要的是应该意识到作用域是按代码文本来确定的:在一个模块内定义的函数的全局作用域就是该模块的命名空间,无论该函数从什么地方或以什么别名被调用。 另一方面,实际的名称(变量)搜索是在运行时动态完成的 — 但是,Python 正在朝着“编译时静态名称解析”的方向发展,因此不要过于依赖动态名称解析! (事实上,局部变量已经是被静态确定了。)这部分了解一下就行。
4. 全局变量 VS 局部变量
全局变量通常是指在函数外定义的变量,它的引用和赋值都是跟模块的命名空间(映射表)关联,所以它的声明生命周期和模块一样,直到程序结束执行,或者这个全局变量被删除,它就从映射表中被删除。
局部变量通常是指函数内定义的变量。这个函数被调用时,创建这个函数的命名空间(映射表),函数调用结束时删除。
举个栗子:
city = '武汉' # 这是全局变量,作用域是全局,放在模块命名空间(映射表)def my_info():name = 'Jock' # 这是局部变量,作用在局部print(f"打印局部变量name:{name}")print(f"打印全局变量city:{city}") # 这时city变量是在模块命名空间(映射表)找到my_info()
print(f"在函数外打印全局变量city:{city}")# NameError: name 'name' is not defined,因为name变量作用在函数my_info,
# 函数调用结束,函数的命名空间被删除,name 不存在了
# print(f"在函数外打印局部变量name:{name}")输出结果:
打印局部变量name:Jock
打印全局变量city:武汉
在函数外打印全局变量city:武汉
这里说明一下,name 是局部变量,放在 my_info 函数的命名空间,只作用于函数里面,所以无法在函数外使用。
如果在函数内尝试修改全局变量的话,那么 Python 会在函数内重新创建一个新的局部变量,这个变量的名字和全局变量名字一样,只不过局部变量放在函数命名空间,全局变量放在模块命名空间。
测试如下:
city = '武汉' # 这是全局变量,作用域是全局,放在模块命名空间(映射表)def my_info():city = '桂林'print(f"函数内变量city的内存地址是:{id(city)}")print(f"在函数内打印变量city:{city}")my_info()
print(f"函数外变量city的内存地址是:{id(city)}")
print(f"在函数外打印变量city:{city}")输出结果:
函数内变量city的内存地址是:2619120672560
在函数内打印变量city:桂林
函数外变量city的内存地址是:2619120920496
在函数外打印变量city:武汉
可以发现函数里的变量 city 和函数外的变量 city 内存地址不一样,说明是不同的两个对象。
那么如何实现在函数内修改全局变量呢?接下来就介绍两个关键字 global
和 nonlocal
5. global
VS nonlocal
global
:用于声明全局变量,可以在函数内声明全局变量,然后实现对全局变量的修改。
举栗子:
city = '武汉' # 这是全局变量,作用域是全局,放在模块命名空间(映射表)def my_info():global city # 声明city为全局变量city = '桂林' # 这时修改的是全局变量print(f"函数内变量city的内存地址是:{id(city)}")print(f"在函数内打印变量city:{city}")my_info()
print(f"函数外变量city的内存地址是:{id(city)}")
print(f"在函数外打印变量city:{city}")输出结果:
函数内变量city的内存地址是:2042908827440
在函数内打印变量city:桂林
函数外变量city的内存地址是:2042908827440
在函数外打印变量city:桂林
可以发现,成功实现了对全局变量的修改。
使用 global 有一些注意事项:
- global 后声明的变量在本作用域中必须没有被使用。
global city
和city = '桂林'
互换位置会报错。这有点必须先声明后使用的感觉。 - global 后声明的变量不能用于函数的正式形参、 for 循环的控制目标、class 定义、函数定义、import 语句和变量标注。这部分的话,没啥经验,我也不太清楚,读者有知道的,欢迎留言交流。
nonlocal
:在嵌套函数中,在内层函数声明 nonlocal 可以实现修改外层函数的局部变量值。
举栗子:
def my_info():city = "武汉" # 外层函数局部变量def my_city():city = "桂林"print(f"在内层函数变量city的内存地址是:{id(city)}")print(f"在内层函数打印变量city:{city}")my_city()print(f"在外层函数变量city的内存地址是:{id(city)}")print(f"在外层函数打印变量city:{city}")my_info()结果输出:
在内层函数变量city的内存地址是:1265469990832
在内层函数打印变量city:桂林
在外层函数变量city的内存地址是:1265469742896
在外层函数打印变量city:武汉
可以发现,在嵌套函数中,外层函数中的变量,相对于内层函数来说,只可读不可修改。在内层函数尝试修改外层函数的变量,会在内层函数重新创建一个新的局部变量,而外层函数的变量依然存在。注意,在内部函数中不能先访问外部函数变量,之后再定义一个同名的局部变量,这样也会报错。如下:
def my_info():city = "武汉" # 外层函数局部变量def my_city():print(f"在内层函数访问外层函数变量city:{city}")city = "桂林"print(f"在内层函数变量city的内存地址是:{id(city)}")print(f"在内层函数打印变量city:{city}")my_city()print(f"在外层函数变量city的内存地址是:{id(city)}")print(f"在外层函数打印变量city:{city}")my_info()输出结果:
UnboundLocalError: local variable 'city' referenced before assignment
即 Python 解释器会认为你的局部变量没有赋值定义就使用了,所以报错。
那么如何实现内部函数修改外部函数局部变量呢?通过 nonlocal 实现。
def my_info():city = "武汉" # 外层函数局部变量def my_city():nonlocal citycity = "桂林"print(f"在内层函数变量city的内存地址是:{id(city)}")print(f"在内层函数打印变量city:{city}")my_city()print(f"在外层函数变量city的内存地址是:{id(city)}")print(f"在外层函数打印变量city:{city}")my_info()输出结果:
在内层函数变量city的内存地址是:2628557791024
在内层函数打印变量city:桂林
在外层函数变量city的内存地址是:2628557791024
在外层函数打印变量city:桂林
可以发现,通过 nonlocal 成功实现了在内层函数修改外层函数局部变量。
使用 nonlocal 需要注意:nonlocal 后面跟着的变量必须是外层函数的局部变量,否则会报错。SyntaxError: no binding for nonlocal 'XXX' found
。如:
def my_info():city = "武汉" # 外层函数局部变量def my_city():nonlocal citynonlocal namecity = "桂林"print(f"在内层函数变量city的内存地址是:{id(city)}")print(f"在内层函数打印变量city:{city}")my_city()print(f"在外层函数变量city的内存地址是:{id(city)}")print(f"在外层函数打印变量city:{city}")my_info()输出结果:
SyntaxError: no binding for nonlocal 'name' found
下面展示一个官方文档给的例子,如果能够看明白,下面的例子,相信大家已经彻底搞明白了 global
和 nonlocal
的用法,对于作用域和命名空间也有了更深的认识。
global 和 nonlocal 的用法:
def scope_test():def do_local():spam = "local spam"def do_nonlocal():nonlocal spamspam = "nonlocal spam"def do_global():global spamspam = "global spam"spam = "test spam"do_local()print("After local assignment:", spam)do_nonlocal()print("After nonlocal assignment:", spam)do_global()print("After global assignment:", spam)scope_test()
print("In global scope:", spam)输出结果:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
说明:
可以看到内部函数 do_local() 不能修改外部函数 scope_test() 的局部变量,如果内部函数要修改外部函数的局部变量,需要用 nonlocal 关键字。通过 do_nonlocal() 的方式可以实现修改外部函数 scope_test() 的局部变量 spam,所以 spam 经过 do_nonlocal() 变为了 nonlocal spam
。内部函数 do_global() 中用 global 关键字声明了一个全局变量,然后对全局变量 spam 进行赋值(如果已存在全局变量 spam,则会修改原全局变量值),但是这不会影响 scope_test() 函数中局部变量 spam 的值,因为这两个 spam 存放在不同的命名空间,外层函数中的 spam 依然是 nonlocal spam
。
Python 的一个特殊规定是这样的:如果不存在生效的 global 或 nonlocal 语句 – 则对名称的赋值总是会进入最内层作用域。 赋值不会复制数据 — 它们只是将名称(变量)绑定到对象。 删除也是如此:语句 del x 会从局部作用域所引用的命名空间中移除对 x 的绑定。 有点像给一个对象贴标签(赋值)和撕标签(删除)的过程。
事实上,所有引入新名称的操作都是使用局部作用域:特别地,import 语句和函数定义会在局部作用域中绑定模块或函数名称。
6. 小结
- 命名空间是一个名字(变量)和对象的映射表。
- 作用域是指命名空间的作用范围,或者说管辖区域。
- 变量的查找遵循
LEGB
原则,先从基层(最内层函数找),然后到市委(外层函数)…,再到省委(模块命名空间),最后到中央(builtin 命名空间)。 - 各个命名空间相互独立,创建时间和生命周期各不相同。
- global 用于在函数内创建和修改全局变量。
- nonlocal 用于在内层函数修改外层函数局部变量。
- 没有声明 global 和 nonlocal,尝试修改全局变量或外层函数局部变量,实际上只会在函数或者内层函数创建一个新的局部变量,同名的全局变量或者外层函数局部变量不会受影响。
7. 巨人的肩膀
- Pytho 官网作用域和命名空间英文版
- The global statement and The nonlocal statement
推荐阅读:
- 编程小白安装Python开发环境及PyCharm的基本用法
- 一文了解Python基础知识
- 一文了解Python数据结构
- 一文了解Python流程控制
- 一文了解Python函数
- 一文了解Python部分高级特性
- 一文了解Python的模块和包
后记:
我从本硕药学零基础转行计算机,自学路上,走过很多弯路,也庆幸自己喜欢记笔记,把知识点进行总结,帮助自己成功实现转行。
2020下半年进入职场,深感自己的不足,所以2021年给自己定了个计划,每日学一技,日积月累,厚积薄发。
如果你想和我一起交流学习,欢迎大家关注我的微信公众号每日学一技
,扫描下方二维码或者搜索每日学一技
关注。
这个公众号主要是分享和记录自己每日的技术学习,不定期整理子类分享,主要涉及 C – > Python – > Java,计算机基础知识,机器学习,职场技能等,简单说就是一句话,成长的见证!