作用域与命名空间
命名空间:是一个从名字到对象的映射
作用域:一个命名空间可直接访问的 Python 程序的文本区域
几条规则
- 命名空间当前是用字典实现(笔者所用为python 3.8),函数、类、模块都有自己的命名空间。
- 模块中的名称为全局名称,函数内定义的名称为通常为局部名称。
- 当存在多个命名空间的时候(通常是3到4个),嵌套作用域搜索顺序遵循:最内层的作用域包含局部名称 > 外层作用域包含非局部名称和非全局名称 > 倒数第二层作用域包含模块的全局名称 > 最外层的作用域为包含内置名称。当然这一搜索顺序会被global和nonlocal声明所改变。
不同命名空间的生存期
- 包含内置名称的命名空间是在 Python 解释器启动时创建的,永远不会被删除。
- 模块的全局命名空间在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出。
- 被解释器的顶层调用执行的语句,从一个脚本文件读取或交互式地读取,被认为是 main 模块调用的一部分,因此它们拥有自己的全局命名空间。
- 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。当然,每次递归调用都会有它自己的本地命名空间。
!!!因此,无法从外部命名空间访问内部命名空间的对象,。
nonlocal和global的用法
如果不存在生效的 global 或 nonlocal 语句 – 则对名称的赋值总是会进入最内层作用域。
global 语句:要重新绑定在全局作用域中找到的变量,可以使用 global 语句声明为全局变量,再进行绑定。
nonlocal 语句:要重新绑定在最内层作用域以外找到的变量,可以使用 nonlocal 语句声明为非本地变量,再进行绑定。
# 示例代码1
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam" # 在外层作用域创建了局部变量spam
do_local() #在最内层的作用域中创建了局部变量spam,执行完即关闭
print("After local assignment:", spam) # 打印局部变量spam,外层的局部变量和最内层的嵌套局部变量spam虽然同名但绑定到了不同的值,而且最内层的局部命名空间已关闭,无法访问。
do_nonlocal() # 将在外层作用域作用域中找到的名称spam绑定到新值
print("After nonlocal assignment:", spam) # 打印当前局部变量spam
do_global() # 创建全局变量spam
print("After global assignment:", spam) # 在函数体内,引用当前作用域
scope_test()
print("In global scope:", spam) # 函数体之外,无法访问内部对象,所以引用当前模块的spam变量——全局变量
# 输出
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
# 示例代码二
# 利用闭包返回一个计数器函数,每次调用它返回递增整数:
# -*- coding: utf-8 -*-
def createCounter():
num = 0
def counter():
nonlocal num #重新绑定在最内层作用域以外找到的变量num需要申明为nonlocal
num =+ num + 1
return num
return counter
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
廖雪峰老师的Python教程-返回函数
Python官方文档-Python作用域与命名空间
菜鸟教程-Python3 命名空间和作用域