本文已参与「新人创作礼」活动,一起开启掘金创作之路
1. 计数器的实现
需求:我们想要实现一个函数add1,这个函数每次运行都会改变一个变量的值,并返回这个变量改变后的当前值。比如,第一次运行add1把变量的值变成1,函数返回1;第二次运行时把变量的值改为2,并返回2。
我们写出一个原型,但Python语法不允许像下面这样在函数内部访问并修改外部的值:
counter = 0
def add1():
counter += 1
return counter
print(add1()) # expected: return = 1, counter = 1; but failed
print(add1()) # expected: return = 2, counter = 2; but failed
以上代码在执行时会报错,表示解释器将函数体内部对counter的mention认为是一个函数内部作用域的局部变量counter,因找不到这个变量而报错。即,默认函数体内部是不能引用外部作用域的变量的。
这个时候我们可以用global关键字,指明add1中对counter的mention指的是全局变量counter:
counter = 0
def add1():
global counter
counter += 1
return counter
print(add1()) # return = counter = 1
print(add1()) # return = counter = 2
print(counter) # 2
2. 封装成计数器函数
当把计数器的逻辑封装成函数时,简单地把上面的过程塞进一个函数,我们大概想要一个这样的原型:
def add1():
counter = 0
def add1c():
???1 # 使counter在子函数内部能被访问到
counter += 1
return counter
return ???2 # 使counter值被改变的情况反馈到外层
print(add1()) # expected: 1
print(add1()) # expected: 2
对于第一处需求,类似上面的global关键字,我们使用nonlocal关键字,代码大致跟上面相似。
global关键字:语法是
global name,表示函数体内对name的mention指向名字为name的全局变量,而不是局部作用域中的变量。nonlocal关键字:语法是
nonlocal name,表示函数体内对name的mention指向名字为name的外层作用域(非全局)中的变量,而不是局部作用域中的变量。
对于第二处需求,考虑到add1内的add1c函数,每次调用这个函数会让counter的值增加;这个函数就是我们最终想要的,所以我们让返回值直接为add1c这个函数,然后在后面的过程中,先调用add1使add1c实例化,再多次调用这个实例化的add1c函数,来实现每次计数器+1的效果。
def add1():
counter = 0
def add1c():
nonlocal counter
counter += 1
return counter
return add1c
a = add1() # add1c函数的实例
print(a()) # 1
print(a()) # 2
在这段代码里,可以从控制台看到变量a的类型是<class 'function'>,值是<function add1.<locals>.add1c at 0x7f8bba5d4a60>,即a是函数,是add1c的一个实例。在下面调用a时,每次a修改函数内部私有的变量,并返回变量的值;每次返回的值不同。
3. 闭包
注意到这里,我们使函数返回自身内部定义的子函数。这就是闭包的概念。
对于外部函数的作用域变量,包括函数的参数和函数体内部定义的局部变量,Python闭包只能引用值,不能修改;但通过nonlocal关键字,就可以实现子函数内修改外部变量。
闭包的核心思想在于,获得一个函数,这个函数可以访问函数上一层作用域的变量。这些变量受这个函数的作用域保护,只能通过这个函数的上层函数修改,从而使得函数拥有私有的变量。
参考
zhuanlan.zhihu.com/p/22229197 介绍闭包概念
www.cnblogs.com/tallme/p/11… 使用了nonlocal+闭包的计数器实现