Python命名空间和作用域

Python命名空间和作用域

经常搞不清楚Python的命名空间和作用域,查阅了一些网上的资料,并自行做了一些测试,将结果整理成本笔记。

文章目录

  • Python命名空间和作用域
    • 命名空间
    • 作用域
    • 全局变量和局部变量
      • `global`和`nonlocal`关键词
      • 模块变量的作用域

命名空间

命名空间是从名称到对象的映射,Python大部分的命名空间都是通过字典来实现的,它的键就是变量名,它的值就是那些变量的值,核心作用是避免名字冲突。

  • python中有三个命名空间
    • 局部命名空间(local namespace):记录了函数的参数和局部定义的变量(类中定义也是),通过locals()查看局部命名空间
    • 全局命名空间(global namespace):记录了模块的变量,包括函数,类,其他导入的模块,模块级的变量和常量,通过globals()查看全局命名空间
    • 内置命名空间(build-in):存放内置的函数和异常,例如:abs()
  • 命名空间查找顺序
    • 局部的命名空间 -> 全局命名空间 -> 内置命名空间
  • 命名空间的生命周期
    • 内置命名空间,python解释器启动时创建,退出时销毁
    • 全局命名空间,模块定义被解释器读入时创建,解释器退出时销毁
    • 局部命名空间,函数调用时创建,函数返回或者异常时销毁
      Python命名空间和作用域_第1张图片

作用域

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

  • Python 的作用域一共有4种
    • 局部作用域:包含局部变量,比如一个函数/方法内部
    • 嵌套作用域:包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal
    • 全局作用域:当前脚本的最外层,比如当前模块的全局变量
    • 内建作用域:包含了内建的变量/关键字等
  • 作用域链
    • 规则顺序: L –> E –> G –> B
  • Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问
g_count = 0 # 全局作用域 
def outer(): 
    o_count = 1 # 闭包函数外的函数中 
    def inner(): 
        i_count = 2 # 局部作用域

Python命名空间和作用域_第2张图片

全局变量和局部变量

  • 定义
    - 全局变量:所有函数之外定义的变量
    - 局部变量:函数内部定义的变量,或者类、模块里的变量

函数内部的变量名如果第一次出现,且出现在=前面,即被视为定义了一个局部变量,不管全局域中有没有该变量名,函数中使用的将是局部变量。从下方的示例中可以看到,在函数内部无法修改全局变量值:

num = 1 # 全局变量
def fun1():
    num = 123 # 局部变量
    print(num) # 输出结果 123
fun1()
123

以下示例为嵌套作用域,同样,局部作用域无法修改嵌套作用域中变量:

def outer():
    num = 10
    def inner():
        num = 100
        print(f'inner print:{num}')
        print(f'inner locals:{locals()}')
    inner()
    print(f'outer print:{num}')
    print(f'outer locals:{locals()}')
outer()
inner print:100
inner locals:{'num': 100}
outer print:10
outer locals:{'num': 10, 'inner': .inner at 0x000002CED4813820>}

globalnonlocal关键词

当内部作用域想修改外部作用域的变量时用到。

  • global:修改全局变量
  • nonlocal:修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量

以下示例通过global关键字声明,修改全局变量:

"""
以下实例修改全局变量num
"""
 
num = 1
def fun1():
    global num # 需要使用 global 关键字声明,如无名为num的全局变量,则重新定义
    print(num) # 输出结果 1
    num = 123
    print(num) # 输出结果 123
fun1()
1
123

输出结果123,说明修改了全局变量:

print(num)
123

以下示例通过nonlocal关键字声明,修改嵌套作用域变量:

"""
以下实例修改嵌套作用域变量num
"""
 
def outer():
    num = 10
    def inner():
        nonlocal num # nonlocal关键字声明
        num = 100
        print(num)
    inner()
    print(num)
outer()
100
100

模块变量的作用域

定义test_module.py, 其中将a赋值为3。如下命令可以在notebook中查看.py文件:

# %load test_module.py
a = 3
  • 其他模块的变量,在当前模块导入后,在当前模块的任何地方,包括函数都可以通过 模块.变量 访问,包括读写:
# 导入模块
import test_module
def f1(): 
    test_module.a = 5
print(test_module.a)
3
f1()

执行f1()后,模块变量a的值被成功修改

# 执行`f1()`后,模块变量`a`的值被成功修改
print(test_module.a)
5

在嵌套函数内,也可以读写导入模块的变量:

def f2(): 
    def f22(): 
        print(test_module.a)
    f22()
f2()
5
def f2(): 
    def f22(): 
        test_module.a = 6
        print(test_module.a)
    f22()
f2()
6
print(test_module.a)
6
  • import 导入模块,多次执行, 实际只执行第一次,可以看到a的值没有变化
import test_module  # 再次执行导入模块,不会再执行了 
print(test_module.a)
6
  • reload重新加载后,a的值重置为3
from importlib import reload
reload(test_module)  # reload 重新真正运行模块 

print(test_module.a)
3
  • 在本模块头部引用外部模块变量,所有函数都能访问。 如果只在本模块的某个函数中导入外部模块变量 ,只在该函数中访问, 在本模块、本模块其他函数中都无法访问外部模块变量
def f3():
    import test_module
    print(test_module.a)
f3()
3
print(test_module.a)
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

Input In [3], in ()
----> 1 print(test_module.a)


NameError: name 'test_module' is not defined
  • 本模块的变量, 在函数中访问时,第一次出现是读操作,直接使用
b = 8
def f4(): 
    print(b)  # 第一次使用 b 是 读取,是全局变量 
f4()
8
print(b)
8
  • 本模块的变量,在函数中访问时,第一次出现是写操作,必须global 声明,否则变成局部变量
def f5(): 
    b = 9    # 第一次使用 b 是 赋值,是函数局部变量 
f5()
print(b)
8
def f5(): 
    global b  # 强制全局变量 
    b = 9 
f5()
print(b)
9

• 参考资料:
o Python3 命名空间和作用域 | 菜鸟教程
o python进阶之命名空间与作用域 - 金色旭光 - 博客园
o python 归纳 (六)_模块变量作用域 - sunzebo - 博客园

你可能感兴趣的:(Python基础,python,开发语言)