Python名称空间与作用域及闭包函数
名称空间
存放名字与值的内存地址对应关系的空间。
名称空间分为三类
内置名称空间:
存放Python解释器内置的名字,如input,print,list等。
存活周期:Python解释器启动则产生,Python解释器关闭则回收,只存在一个。
全局命名空间:
运行顶级代码所定义的名字,或者说不是函数内定义、也不是内置的,剩下的都是全局名称空间。
存活周期:Python文件执行产生,在定义全局名称时会在全局名称空间内将名称放进去,文件执行结束立即回收,只存在一个。
# 直接在一行开头定义一个变量,或者定义一个函数,那么这个变量名与函数名就属于全局名称空间。
x = 10
def func():
pass
# 流程控制语句缩进内的定义的名称,也是属于全局名称空间。
if 10 > 3:
a = 111
局部名称空间:
在函数体的运行中开辟的局部的空间,也叫做临时命名空间,同一个函数调用多次会产生多个局部名称空间。
存活周期:函数体执行时产生,函数运行结束,这个空间也会回收。
# 函数内部定义的名称。
def func(): # func本身是全局名称空间。
x = 10 # x是局部名称空间。
func()
名称空间实际上是相互隔离独立的一个个空间,并没有包含关,嵌套关系仅为了帮助理解。
加载到内存的顺序:
-
1.内置名称空间
-
2.全局名称空间
-
3.临时名称空间
取值顺序:
按照LEGB原则就近取值,取值顺序单向不可逆。
LEGB原则:
- Local本地 --> Enclosed嵌套函数的外层函数内部 --> Global全局 --> Builtin内置
(从局部开始找时)局部名称空间 --> 全局名称空间 --> 内置名称空间
input = 333
print(input) # 此时是从全局开始取值,input()函数则不会被查找到
# 333
名称空间的嵌套关系是在函数定义阶段,即检测语法时确定的,与函数调用的位置无关,与函数定义位置有关。
函数的取值位置是个很绕的知识点,最好一步一步分析函数的执行步骤,与惯性思维做斗争。举几个例子帮助学习。
示例一:
x = 111 # 1、首先在全局定义x = 111
def func(): # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
print(x) # 5、x向全局取值,此时x = 222,所以最终结果为222。
x = 222 # 3、此时全局定义x = 222
func() # 4、调用func
222
x = 111 # 1、首先在全局定义x = 111
def func(): # 2、定义func,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
print(x) # 4、x向全局取值,此时x = 111,所以最终结果为111。
func() # 3、调用func。
x = 222 # 5、此时在全局定义x = 222,但func函数已经调用,函数内的x已经取值为111。
111
示例二:
x = 1 # 1、在全局定义x = 1
def func(): # 2、定义func函数,此时已确定x取值先从func局部取值,局部未找到则向全局取值。
print(x) # 7、打印x,x此时从全局取值,结果为1
def foo(): # 3、定义foo函数
x = 222 # 5、在foo的局部名称空间内定义x = 222,并不会干扰到全局的x
func() # 6、调用func
foo() # 4、调用foo
1
示例三:
input = 111 # 1、在全局定义input = 111。
def f1(): # 2、定义f1函数。
def f2(): # 4、在f1局部名称空间内定义f2。
input = 333 # 7、在f2局部名称空间内定义input = 333。
print(input) # 8、input先在当前所在名称空间内取值,此时input值为333。
input = 222 # 5、在f1局部名称空间内定义。
f2() # 6、调用f2,执行函数体代码。
f1() # 3、调用f1,执行f1函数体代码。
333
示例四:
count = 1
def func():
count = 100
print(count) # 直接在当前名称空间取值,count为100。
func()
100
示例五:
def f1():
m=111
return m
def f2():
print(res) # 此时已确定res取值先从f2局部取值,局部未找到则向全局取值。
def f3():
print(res) # 此时已确定res取值先从f3局部取值,局部未找到则向全局取值。
res=f1() # f1() = m = 111
f2()
f3()
111
111
示例六:
x=111
def f1():
print(x) # 定义阶段已经确定先从局部取值,但是局部x是先取值再定义,所以保存
x = 222
f1()
UnboundLocalError: local variable 'x' referenced before assignment
示例七:
m = [111,]
def func(y):
m.append(333)
print(y)
func(m)
m.append(222)
print(m)
[111, 333]
[111, 333, 222]
名称空间不仅是Python独有,这是整个计算机领域的一种设计,在Python之禅的最后一句话。
import this
...
Namespaces are one honking great idea -- let's do more of those!
作用域:
变量的生效范围,分为全局作用域和局部作用域。
全局作用域:
- 内置名称空间,全局名称空间。全局存活,全局有效。
# x就属于全局名称空间,在全局名称空间和局部名称空间都能访问到。
x = 10
def f1():
print(x)
print(x)
f1()
10
10
globals()
返回包含当前范围的全局变量的字典。
x = 111
print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000000002061940>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/desktop/code.py', '__cached__': None, 'x': 111}
局部作用域:
- 局部名称空间,临时存活,局部有效。
- 局部作用域可以引用全局作用域的变量,但不可改变。
x = 10
def f1():
x = 20
f1()
print(x)
locals()
返回包含当前作用域的局部变量的字典。
x = 111
def func():
a = 10
b = 20
print(locals())
func()
{'a': 10, 'b': 20}
global
只能在局部名称空间使用。在局部声明一个全局作用域的变量。在 global
语句中列出的名称不得在同一代码块内该 global
语句之前的位置中使用。
在 global
语句中列出的名称不得被定义为正式形参,不也得出现于 for
循环的控制目标、class
定义、函数定义、import
语句或变量标注之中。
局部名称空间不能修改全局名称空间的不可变数据类型的值,只能引用。
c = 1
def func():
c += 1 # 不可更改,在更改时会从先局部名称空间取c的值,然后再赋值给c,造成先引用、后定义的保错
print(c)
func()
# local variable 'count' referenced before assignment
对于可变类型,也不能使用先取值后修改再赋值给原变量名这种方式,而是使用内置方法来修改。
c = [1,2,3]
def func():
c += [3,4,5] # 相当于 c = c + [3,4,5]
print(c)
func()
UnboundLocalError: local variable 'c' referenced before assignment
c = [1,2,3]
def func():
c.extend([3,4,5])
print(c)
func()
[1, 2, 3, 3, 4, 5]
使用global关键字,可以在局部修改一个全局变量。
c = 1
def func():
global c
c += 1
print(c)
func()
2
nonlocal
只能在函数嵌套定义的内层函数中使用,在第一层局部名称空间内使用会报错。用作函数嵌套定义中,在内层函数中声明一个外层局部名称空间的变量。
nonlocal
语句中列出的名称不得与之前存在于局部作用域中的绑定相冲突。
x = 111
def f1():
x = 222
def f2():
nonlocal x
x = 333
f2()
print(x)
f1()
333
闭包函数定义:
- 闭函数:是嵌套在函数中的函数。
- 包函数:内层闭函数引用外层函数一个非全局的变量,就是闭包。
def f1():
x = 10
def f2():
print(x)
闭包的作用:
被引用的非全局变量也称作自由变量,这个自由变量会与内层函数产生一个绑定关系。
自由变量不会在内存中消失。
1、保证数据安全。函数外无法访问自由变量。
def func1():
li = []
def func2(x):
li.append(x)
return li
ruturn func2
f = func1()
print(f(100)) # [100]
print(f(100)) # [100, 100]
print(f(100)) # [100, 100, 100]
#li就是自由变量,并没有消失,但无法从函数外访问。
2、为内部函数传参。
返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。
def f1(x):
def f2():
print(x,y) # 优先使用外层的作用域,不会被全局所影响。
x = 10
y = 20
return f2
x = 1
y = 2
f1(x)()
10 20
如何判断一个嵌套函数是不是闭包:
print(func.__code__.co_freevars)
# 只要返回值有自由变量那么就是闭包
def func():
x = 10
def f1():
print(x)
return f1
print(func().__code__.co_freevars)
('x',)