Python 作用域 {scope} 与闭包 {closure}
- [1. Built-in Functions: `globals()`](#1. Built-in Functions:
globals()
) - [2. Python 作用域 (scope)](#2. Python 作用域 (scope))
- [2. Nested function in Python](#2. Nested function in Python)
- [3. Python 函数作为函数返回值](#3. Python 函数作为函数返回值)
- [4. Python 闭包 (closure)](#4. Python 闭包 (closure))
- [5. Python 作用域的同名互斥性](#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-8
print(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-8
print(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"
break
print(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-8
print(f"type(globals()): {type(globals())}")
print(f"len(globals().keys()): {len(globals().keys())}")
print(f"globals().keys(): {globals().keys()}")
def func():
local_var = 9
print(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-8
def globals():
print(f"from local globals()")
return True
print(f"globals(): {globals()}")
系统内建的函数 globals()
被我们自定义的同名函数拦截,如果我们没有在全局作用域中定义此处的 globals()
,则会去内建作用域中查找。
/home/yongqiang/miniconda3/bin/python /home/yongqiang/stable_diffusion_work/stable_diffusion_diffusers/yongqiang.py
from local globals()
globals(): True
Process 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-8
def greet(name):
# inner function
def display_name():
print(f"Hi, {name}!")
# call inner function
display_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-8
def 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=123
Process finished with exit code 0
- 内嵌函数中定义的变量只可在内嵌函数内使用。
- 内嵌函数中可以访问外部函数定义的变量。如果内嵌函数中定义的变量与外部函数中变量重名,那么内嵌函数的作用域优先级最高。
变量的查找过程就像一条单向链一样,逐层向上,要么找到变量的定义,要么报错未定义,这种作用域机制称为作用域链。
3. Python 函数作为函数返回值
函数名实际上就是一个变量,它指向了一个函数对象,所以可以有多个变量指向一个函数对象,并引用它。
#!/usr/bin/env python
# coding=utf-8
func_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 = 4
Process finished with exit code 0
-
Python 中没有块作用域,当循环结束以后,循环体中的临时变量
i
作为全局变量不会销毁,它的值是 2。 -
Python 在把函数作为返回值时,并不会把函数体中的全局变量替换为实际的值,而是原封不动的保留该变量。
#!/usr/bin/env python
coding=utf-8
func_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 = 4
Process finished with exit code 0
4. Python 闭包 (closure)
在 Python 中,如果在一个内部函数中,对定义它的外部函数的作用域中的变量 (甚至是外层之外,只要不是全局变量,即内嵌函数中还可以嵌套定义内嵌函数) 进行了引用,那么这个子函数就被认为是闭包。
闭包 = 内嵌函数 + 内嵌函数引用的变量环境
闭包具有以下两个显著特点:
-
闭包是函数内部定义的内嵌函数。
-
闭包引用了闭包作用域之外的变量,但非全局变量。
#!/usr/bin/env python
coding=utf-8
def offset_func(n):
base = n
print(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 + i return step_func
offset_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:
101
Process finished with exit code 0
5. Python 作用域的同名互斥性
Python 语言规则指定,所有在赋值语句左边的变量名如果是第一次出现在当前作用域中,都将被定义为当前作用域的变量。
作用域的同名互斥性是指在不同的两个作用域中,若定义了同名变量,那么高优先级的作用域中不能同时访问这两个变量,只能访问其中之一。
#!/usr/bin/env python
# coding=utf-8
var = 0
def func():
var = 1
print(f"var: {var}")
global var
print(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 11
global var
^^^^^^^^^^
SyntaxError: name 'var' is used prior to global declaration
Process finished with exit code 1
#!/usr/bin/env python
# coding=utf-8
def outer_func():
val = 0
def inner_func():
val = val + 1 # Or val += 1
return val
return inner_func
obj = 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_func
val = val + 1 # Or val += 1
^^^
UnboundLocalError: cannot access local variable 'val' where it is not associated with a value
Process finished with exit code 1
nonlocal
声明可以在闭包中声明使用上一级作用域中的变量。使用 nonlocal
声明 val
为上一级作用域中的变量 val
,就解决了该问题,可以实现累加了。注意 nonlocal
关键字只能用于内嵌函数中,并且外层函数中定义了相应的局部变量,否则报错。
#!/usr/bin/env python
# coding=utf-8
def outer_func():
val = 0
def inner_func():
nonlocal val
val = val + 1 # Or val += 1
return val
return inner_func
obj = 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: 2
Process finished with exit code 0
References
1\] Yongqiang Cheng,