1、global关键字的作用

如果在函数中需要修改全局变量,则需要使用该关键字,具体参见下面例子。

variable=100
def function():
print(variable) #在函数内不对全局变量修改,直接访问是没问题的,不会错
function() #输出100

variable=100
def function():
result=variable+111
print(result) #在函数内不对全局变量修改,直接使用是没问题的,不会报错
function() #输出211

variable=100
def function():
variable+=111
print(variable) #显示local variable ‘variable’ referenced before assignment。#即在函数局部作用域中直接改变全局变量的值会报错
function()

variable=100
def function():
variable=1000 #此时修改variable变量的值不会报错,因为已经在函数局部作用域内重新定义variable,会覆盖全局variable。
variable+=111
print(variable)
function() #输出1111
print(variable) #输出100,虽然函数内部重新覆盖了variable,但是全局variable并未变,依然还是100

那如果不再函数内部重新为全局变量赋值,又想改变全局变量的值,应该怎么做呢?这就要使用global关键字了,如下。

variable=100
def function():
global variable #使用global关键字,表明variable是全局的,此时就可以了直接在函数局部作用域内改变variable的值了
variable+=111
print(variable) #输出211
function()
print(variable) #输出211,这和上面的不一样了,发现全局变量variable本身也改变了

总结:global的作用就是在“函数局部作用域”内声明表示一个全局变量,从而可以在函数内部修改全局变量的值(否则只能访问不能修改),而且函数内部改变的全局变量的值也会改变。

2、函数局部作用域

函数的局部作用域是不能够保存信息的,即在函数内部声明变量在函数调用结束之后函数里面保存的信息就被销毁了,包括函数的参数,如下。

def fun(step):
num=1
num+=step
print(num)
i=1
while(i<5):
fun(3)
i+=1
连续调用函数4次,每次输出的值都是4,即3+1,这说明每次调用fun函数之后,函数内部定义局部变量num就被销毁了,
有保存下来,说明函数的局部作用域被销毁了。那如果要保存函数的局部变量,怎么办呢?引入“闭包”。

3、闭包——装饰器的本质也是闭包

“闭包”的本质就是函数的嵌套定义,即在函数内部再定义函数,如下所示。
“闭包”有两种不同的方式,第一种是在函数内部就“直接调用了”;第二种是“返回一个函数名称”。

3-1 第一种形式——直接调用

def Maker(name):
num=100
def func1(weight,height,age):
weight+=1
height+=1
age+=1
print(name,weight,height,age)
func1(100,200,300) #在内部就直接调用“内部函数”
Maker(‘feifei’) #调用外部函数,输出 feifei 101 201 301

3-2 第二种形式——返回函数名称

def Maker(name):
num=100
def func1(weight,height,age):
weight+=1
height+=1
age+=1
print(name,weight,height,age)
return func1 #此处不直接调用,而是返回函数名称(Python中一切皆对象)
maker=Maker(‘feifei’) #调用包装器,这里可以理解为返回一个func1的对象,并赋初始值‘feifei’
maker(100,200,300) #调用内部函数

3-3“闭包”的作用——保存函数的状态信息,使函数的局部变量信息依然可以保存下来,如下。

def Maker(step): #包装器
num=1
def fun1(): #内部函数
nonlocal num #nonlocal关键字的作用和前面的local是一样的,如果不使用该关键字,则不能再内部函数改变“外部变量”的值
num=num+step #改变外部变量的值(如果只是访问外部变量,则不需要适用nonlocal)
print(num)
return fun1
j=1
func2=Maker(3) #调用外部包装器
while(j<5):
func2() #调用内部函数4次 输出的结果是 4、7、10、13
j+=1


从上面的例子可以看出,外部装饰器函数的局部变量num=1、以及调用装饰器Maker(3)时候传入的参数step=3都被记忆了下来,所以才有1+3=4、4+3=7、7+3=10、10+3=13
从这里可以看出,Maker函数虽然调用了,但是它的局部变量信息却被保存了下来,这就是“闭包”的最大的作用——保存局部信息不被销毁。

4、nonlocal关键字的作用

该关键字的作用和local的作用类似,就是让“内部函数”可以修改“外部函数(装饰器)”的局部变量值。详细信息这里不做讨论。

5 闭包注意事项

外部函数的局部变量对应内函数的局部变,事实上相当于之前讲的全局变量和局部变量之间的对应关系。即内部函数中只能对外部函数的局部变量进行访问但是不能修改。

如下所示
def funX():
x=5
def funY():
x=x+1
return x
return funY
temp=funX()
temp()

输出产生如下错误:NameError: name ‘x’ is not defined,因为funY()内部函数修改了外部函数的局部变量所以产生了错。

解决方法1:引用不修改

def funX():
x=5
def funY():
y=x+1
print(y)
return funY
temp=funX()
temp()

如上所示结果输出:6,这里funY内部函数只是访问了funX的外部变量X,并没有进行修改。

解决方法2:使用容器类型来存放

容器类型不是放在栈中的因此不会被屏蔽掉。之前介绍的字符串、列表、元祖皆属于容器
def funX():
x=[5]
def funY():
x[0]=x[0]+1
print(x[0])
return funY
temp=funX()
temp()

如上所示结果输出:6,这里使用了列表容器进行了存储。

解决方法3:使用nonlocal关键字

nonlocal关键字修改外部函数的局部变量的值,告诉python这不是一个局部变量。

def funX():
x=5
def funY():
x=x+1
print(x)
return funY
temp=funX()
temp()

如上所示结果输出:6,这里使用了列表容器进行了存储。

6 总结

闭包概念的引入是为了尽可能的避免使用全部变量,闭包允许将函数与其所操作的某些数据(环境)关联起来,这样外部函数就为内部函数构成了一个封闭环境。

7 闭包在实际开发中的小游戏

origin = (0, 0) # 原点
legal_x = [-100, 100] # x轴的移动范围,legal_x列表
legal_y = [-100, 100] # y轴的移动范围,legal_x列表
def create(pos_x=0, pos_y=0):
# 初始化位于原点为主
def moving(direction, step):
# direction参数设置方向,1为向右(向上),-1为向左(向下),0为不移动
# step参数设置移动的距离
nonlocal pos_x, pos_y
new_x = pos_x + direction[0] * step
new_y = pos_y + direction[1] * step
# 检查移动后是否超出x轴边界
if new_x < legal_x[0]:
pos_x = legal_x[0] - (new_x - legal_x[0])
elif new_x > legal_x[1]:
pos_x = legal_x[1] - (new_x - legal_x[1])
else:
pos_x = new_x

# 检查移动后是否超出y轴边界
if new_y < legal_y[0]:
pos_y = legal_y[0] - (new_y - legal_y[0])
elif new_y > legal_y[1]:
pos_y = legal_y[1] - (new_y - legal_y[1])
else:
pos_y = new_y
return pos_x, pos_y
return moving

move = create()
print(‘向右移动10步后,位置是:’, move([1, 0], 10))
print(‘向上移动130步后,位置是:’, move([0, 1], 130))
print(‘向左移动10步后,位置是:’, move([-1, 0], 10))

评 论