当前位置: 首页 > news >正文

Python 作用域 (scope) 与闭包 (closure)

Python 作用域 {scope} 与闭包 {closure}

  • 1. Built-in Functions: `globals()`
  • 2. Python 作用域 (scope)
  • 2. Nested function in Python
  • 3. Python 函数作为函数返回值
  • 4. Python 闭包 (closure)
  • 5. Python 作用域的同名互斥性
  • References

函数和装饰器
https://pythonhowto.readthedocs.io/zh-cn/latest/decorator.html

1. Built-in Functions: globals()

Built-in Functions - globals()
https://docs.python.org/3/library/functions.html

globals() 函数以字典类型返回当前位置的全部全局变量。

Return the dictionary implementing the current module namespace. For code within functions, this is set when the function is defined and remains the same regardless of where the function is called.
对于函数内的代码,这是在定义函数时设置的,无论函数在哪里被调用都保持不变。

#!/usr/bin/env python
# coding=utf-8print(f"type(globals()): {type(globals())}")
print(f"globals(): {globals()}")
/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
type(globals()): <class 'dict'>
globals(): {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7cb23063b110>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py', '__cached__': None}Process finished with exit code 0

2. Python 作用域 (scope)

在 Python 中,代码块结束后依然可以访问块中定义的变量,块作用域是不存在的。代码块中的定义的变量的作用域就是代码块所在的作用域,默认就是全局作用域。

#!/usr/bin/env python
# coding=utf-8print(f"type(globals()): {type(globals())}")dict0 = globals()
print(f"len(dict0.keys()): {len(dict0.keys())}")
print(f"dict0.keys(): {dict0.keys()}")while True:block_var = "yongqiang"breakprint(f"block_var: {block_var}")dict1 = globals()
print(f"len(dict1.keys()): {len(dict1.keys())}")
print(f"dict1.keys(): {dict1.keys()}")

globals() 的返回值中可以看到在代码块执行后,全局变量中出现了 block_var

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
type(globals()): <class 'dict'>
len(dict0.keys()): 10
dict0.keys(): dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'dict0'])
block_var: yongqiang
len(dict1.keys()): 12
dict1.keys(): dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'dict0', 'block_var', 'dict1'])Process finished with exit code 0
#!/usr/bin/env python
# coding=utf-8print(f"type(globals()): {type(globals())}")print(f"len(globals().keys()): {len(globals().keys())}")
print(f"globals().keys(): {globals().keys()}")def func():local_var = 9print(f"local_var: {local_var}")# print(f"local_var: {local_var}")  # NameError: name 'local_var' is not defined
print(f"'local_var' in globals(): {'local_var' in globals()}")print(f"len(globals().keys()): {len(globals().keys())}")
print(f"globals().keys(): {globals().keys()}")

local_var 的作用域在函数内部,函数结束时,局部变量所占的资源就被释放了,外部无法再访问。

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
type(globals()): <class 'dict'>
len(globals().keys()): 9
globals().keys(): dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__'])
'local_var' in globals(): False
len(globals().keys()): 10
globals().keys(): dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'func'])Process finished with exit code 0

Python 中只有模块 (module),类 (class) 以及函数 (def and lambda) 才会引入新的作用域,其它的代码块 (if / elif / else, try / except, for / while 等) 不会引入新的作用域。

Python 的作用域一共有 4 种:

  • L (Locals) 局部作用域或当前作用域
  • E (Enclosing) 闭包函数外的函数中
  • G (Globals) 全局作用域
  • B (Built-ins) 内建作用域

Python 解释器查找变量时按照 L -> E -> G -> B 作用域顺序查找。如果在局部作用域中找不到该变量,就会去局部作用域的上一层的局部作用域找 (在闭包函数中),还找不到就会去全局作用域找,再者去内建作用域中查找。

#!/usr/bin/env python
# coding=utf-8def globals():print(f"from local globals()")return Trueprint(f"globals(): {globals()}")

系统内建的函数 globals() 被我们自定义的同名函数拦截,如果我们没有在全局作用域中定义此处的 globals(),则会去内建作用域中查找。

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
from local globals()
globals(): TrueProcess finished with exit code 0

2. Nested function in Python

In Python, we can create a function inside another function. This is known as a nested function.

在 Python 中函数作为对象存在,函数可以作为另一个函数的参数或返回值,也可以在函数中嵌套定义函数。

#!/usr/bin/env python
# coding=utf-8def greet(name):# inner functiondef display_name():print(f"Hi, {name}!")# call inner functiondisplay_name()# call outer function
greet("yongqiang")

In the above example, we have defined the display_name() function inside the greet() function.

Here, display_name() is a nested function. The nested function works similar to the normal function. It executes when display_name() is called inside the function greet().

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
Hi, yongqiang!Process finished with exit code 0

内部函数只可以在包含它的外部函数中使用,内部函数是局部的。相对于外部函数来说,内部函数是嵌入进来的,又被称为内嵌函数。

#!/usr/bin/env python
# coding=utf-8def outer_func():var0, var1 = "ABC", "DEF"def inner_func():var0 = "abc"var2 = "123"print(f"var0={var0}, var1={var1}, var2={var2}")print(f"var0={var0}, var1={var1}")inner_func()outer_func()
/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
var0=ABC, var1=DEF
var0=abc, var1=DEF, var2=123Process finished with exit code 0
  • 内嵌函数中定义的变量只可在内嵌函数内使用。
  • 内嵌函数中可以访问外部函数定义的变量。如果内嵌函数中定义的变量与外部函数中变量重名,那么内嵌函数的作用域优先级最高。

变量的查找过程就像一条单向链一样,逐层向上,要么找到变量的定义,要么报错未定义,这种作用域机制称为作用域链。

3. Python 函数作为函数返回值

函数名实际上就是一个变量,它指向了一个函数对象,所以可以有多个变量指向一个函数对象,并引用它。

#!/usr/bin/env python
# coding=utf-8func_list = []
for i in range(3):def forever(x):print(f"forever: i = {i}, x = {x}, x+i = {x + i}")func_list.append(forever)for func in func_list:func(2)
/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
forever: i = 2, x = 2, x+i = 4
forever: i = 2, x = 2, x+i = 4
forever: i = 2, x = 2, x+i = 4Process finished with exit code 0
  • Python 中没有块作用域,当循环结束以后,循环体中的临时变量 i 作为全局变量不会销毁,它的值是 2。
  • Python 在把函数作为返回值时,并不会把函数体中的全局变量替换为实际的值,而是原封不动的保留该变量。
#!/usr/bin/env python
# coding=utf-8func_list = []
for i in range(3):def forever(x, y=i):print(f"forever: y = {y}, x = {x}, x+y = {x + y}")func_list.append(forever)for func in func_list:func(2)

i 作为参数传递给函数,将全部变量变成函数内部的局部变量。

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
forever: y = 0, x = 2, x+y = 2
forever: y = 1, x = 2, x+y = 3
forever: y = 2, x = 2, x+y = 4Process finished with exit code 0

4. Python 闭包 (closure)

在 Python 中,如果在一个内部函数中,对定义它的外部函数的作用域中的变量 (甚至是外层之外,只要不是全局变量,即内嵌函数中还可以嵌套定义内嵌函数) 进行了引用,那么这个子函数就被认为是闭包。

闭包 = 内嵌函数 + 内嵌函数引用的变量环境

闭包具有以下两个显著特点:

  • 闭包是函数内部定义的内嵌函数。
  • 闭包引用了闭包作用域之外的变量,但非全局变量。
#!/usr/bin/env python
# coding=utf-8def offset_func(n):base = nprint(f"offset_func: base = {base}, n = {n}")def step_func(i):print(f"step_func: base = {base}, i = {i}, base+i = {base + i}")return base + ireturn step_funcoffset_0 = offset_func(0)
offset_100 = offset_func(100)print(f"\n*********")
print(f"offset_0:\n{offset_0(1)}")
print(f"\n=========")
print(f"offset_100:\n{offset_100(1)}")

在 Python 中,当内嵌函数作为返回值传递给外部变量时,将会把定义它时涉及到的引用环境和函数体自身复制后打包成一个整体返回,这个整体就像一个封闭的包裹,不能再被打开修改,所以称为闭包。

对于 offset_0 来说,它的引用环境就是变量 base = 0 ,以及建立在引用环境上函数体 base + i

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
offset_func: base = 0, n = 0
offset_func: base = 100, n = 100*********
step_func: base = 0, i = 1, base+i = 1
offset_0:
1=========
step_func: base = 100, i = 1, base+i = 101
offset_100:
101Process finished with exit code 0

5. Python 作用域的同名互斥性

Python 语言规则指定,所有在赋值语句左边的变量名如果是第一次出现在当前作用域中,都将被定义为当前作用域的变量。

作用域的同名互斥性是指在不同的两个作用域中,若定义了同名变量,那么高优先级的作用域中不能同时访问这两个变量,只能访问其中之一。

#!/usr/bin/env python
# coding=utf-8var = 0def func():var = 1print(f"var: {var}")global varprint(f"var: {var}")

global 声明的 var 是全局变量,即 global 可以修改作用域链,当访问 var 变量时而直接跳转到全局作用域查找。错误提示在本语句前变量名 var 已经被占用了,所以函数体内的局部作用域内,要么只使用局部变量 var,要么在使用 var 前就声明是全局变量 var

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py File "/home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py", line 11global var^^^^^^^^^^
SyntaxError: name 'var' is used prior to global declarationProcess finished with exit code 1
#!/usr/bin/env python
# coding=utf-8def outer_func():val = 0def inner_func():val = val + 1  # Or val += 1return valreturn inner_funcobj = outer_func()
print(f"obj(): {obj()}")

Python 语言规则指定,所有在赋值语句左边的变量名如果是第一次出现在当前作用域中,都将被定义为当前作用域的变量。

由于在闭包 inner_func() 中,变量 val 在赋值符号 = 的左边,被 Python 认为是 inner_func() 中的局部变量。在接下来执行 obj() 时,程序运行至 val = val + 1 时,因为先前已经把 val 定义为 inner_func() 中的局部变量,由于作用域同名互斥性,右边 val + 1 中的 val 只能是局部变量 val,但是它并没有定义,所以会报错。

/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
Traceback (most recent call last):File "/home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py", line 15, in <module>print(f"obj(): {obj()}")^^^^^File "/home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py", line 8, in inner_funcval = val + 1  # Or val += 1^^^
UnboundLocalError: cannot access local variable 'val' where it is not associated with a valueProcess finished with exit code 1

nonlocal 声明可以在闭包中声明使用上一级作用域中的变量。使用 nonlocal 声明 val 为上一级作用域中的变量 val,就解决了该问题,可以实现累加了。注意 nonlocal 关键字只能用于内嵌函数中,并且外层函数中定义了相应的局部变量,否则报错。

#!/usr/bin/env python
# coding=utf-8def outer_func():val = 0def inner_func():nonlocal valval = val + 1  # Or val += 1return valreturn inner_funcobj = outer_func()
print(f"obj() - 1: {obj()}")
print(f"obj() - 2: {obj()}")
/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py 
obj() - 1: 1
obj() - 2: 2Process finished with exit code 0

References

[1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

http://www.xdnf.cn/news/1324693.html

相关文章:

  • 【学习嵌入式-day-27-进程间通信】
  • Docker常见指令速查
  • 用户认证技术
  • STL库——string(类函数学习)
  • SQL详细语法教程(六)存储+索引
  • AI心理助手开发文档
  • 在python中等号左边的都是对象,在matlab中等号a = 3+2 a就是个变量
  • 力扣hot100:盛最多水的容器:双指针法高效求解最大容量问题(11)
  • openfeign 只有接口如何创建bean的
  • Linux设备树简介
  • vue3入门-v-model、ref和reactive讲解
  • Leetcode 16 java
  • Effective C++ 条款49:了解new-handler的行为
  • 力扣 hot100 Day77
  • 单片机驱动LCD显示模块LM6029BCW
  • 机器翻译论文阅读方法:顶会(ACL、EMNLP)论文解析技巧
  • STM32学习笔记14-I2C硬件控制
  • 大数据计算引擎(四)—— Impala
  • Fluss:颠覆Kafka的面向分析的实时流存储
  • GPT-5之后:当大模型更新不再是唯一焦点
  • 深度学习必然用到的概率知识
  • Vue 3中watch的返回值:解锁监听的隐藏技巧
  • 敏感数据加密平台设计实战:如何为你的系统打造安全“保险柜”
  • 遥感机器学习入门实战教程 | Sklearn 案例②:PCA + k-NN 分类与评估
  • Day8--滑动窗口与双指针--1004. 最大连续1的个数 III,1658. 将 x 减到 0 的最小操作数,3641. 最长半重复子数组
  • 具身智能2硬件架构(人形机器人)摘自Openloong社区
  • Next.js 中的 SEO:搜索引擎优化最佳实践
  • flutter项目适配鸿蒙
  • JMeter与大模型融合应用之构建AI智能体:评审性能测试脚本
  • 【Jenkins】03 - 自动构建和docker构建