在廖雪峰的官网上看到一个很有意思题目。关于闭包的,有兴趣的朋友可以看一下, 做一下这个题目,当然需要一点闭包的知识。


下面我简述一下:

利用闭包返回一个计数器函数,每次调用它返回递增整数。

# 修改下面这个函数def createCounter():   def counter():       pass   return counter
# 测试:counterA = createCounter()print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5counterB = createCounter()if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:    print('测试通过!')else:    print('测试失败!')

方法一

说实话这题对我来说还是有点难度的,但我尝试了几次之后也找到一个比较track的方法。一开始我是这么写的。

def createCounter():    i = 0    def counter(i=i):        i = i+1        return i    return counter# 执行结果是:1 1 1 1 1


这样当然是错的, 因为整数是 不可变对象

,当你作为参数传进去时都会创建一个新的内存空间。 这里边其实还有很多学问,不是很了解的可以看一下stackoverflow上的这个回答: https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference 虽然失败了,但也让我想到一个track的方法, 就是把i换成可变对象。


def createCounter():    i = [0]    def counter():        i[0] = i[0]+1        return i[0]    return counter# 执行结果是:1 2 3 4 5


OK, 这样就没有问题了。 但这并不是一个好的解决方法, 利用可变对象的这个特性有可能会引起变量作用域混乱的。 于是我又想到了另一种解决。 方法二

另一种方法就是使用generator,在createCounter函数下创建一个从1开始的整数generator, 然后在cuonter函数中调用。

由于generator保存的是算法,当调用next函数时就可以计算出下一个的值,直到没有元素报错。当然这里不用担心,generator可以创建无限集合。

def createCounter():    def inter():        n = 1        while True:            yield n            n = n+1    f = inter()    def counter():        return next(f)    return counter


上面的代码中,inter()就是一个包含从1开始的所有整数的generator。 然后在 counter里边调用。 每次计算下一个的值。 这样就可以实现计数的功能。 说到generator,stackoverflow上有一个回答值得一读,即使你已经掌握这个也可以读一下,这个回答应该还是python问答当中排名第一的。 链接在这里:https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do 方法三

emmmm,想到这两种方法已经是极限了,于是我往评论区翻了翻,看一下大佬们有什么做法。然后就看到一个我没见过的关键字……其中有一个大佬是这么做

def creat_counter():    i=0    def counter():        nonlocal i        i=i+1        return i    return counter


学了python这么久,第一次看到nonlocal这个关键字,果然我还是太菜了…… 不过从语句上看nonlocal的作用应该是把i变成全局变量,这样每次修改都可以生效,跟global关键字有点像。既然找到一个知识盲点,那就将它彻底解决吧。 nonlocal与global 说了这么多,是时候回到主题了,nonlocal关键字到底是什么?在什么情况下用呢? 简单来说,nonlocal关键字是用来改变变量的作用域的。

直接解释不太好懂,先来看两个例子吧。

def outside():    msg = "Outside!"    def inside():        msg = "Inside!"        print(msg)    inside()    print(msg)


执行结果是什么呢?

Inside!Outside!


结果应该很好理解, 在outside函数里面定义了inside函数并且执行。 当运行outside函数时,inside里面的msg变量指向了"Inside!",outside里面的msg指向了"Outside!", 也就是说这里其实有两个msg变量,并且指向了不同的值。 如下图所示:

python 怎么将none值转化为null值_python


再来看下面这个例子:

def outside():    msg = "Outside!"    def inside():        nonlocal msg        msg = "Inside!"        print(msg)    inside()    print(msg)


现在的执行结果就变成了:

Inside!Inside!


两段代码之间的差别仅在于下面的例子多了一句  nonlocal msg

。 这里的nonlocal关键字起到了什么作用呢? nonlocal意思是告诉python,不要重新创建msg变量,而是使用outside中的msg变量来赋值。

画个图就很好懂了。

python 怎么将none值转化为null值_python 闭包_02


在这个例子中, msg变量只被创建了一次,首先将"Outside!"赋值给msg,然后将"Inside!"赋值给了msg, 此时的msg已经指向了"Inside!"。因此执行的结果两个都是"Inside!"。 现在我们知道了,nonlocal是用来改变变量的作用域的。本例中,nonlocal将inside函数里面的msg变量的作用域变成了outside块中的区域。nonlocal跟global 这两个关键字非常像,不同之处在于nonlocal用于外部函数作用域的变量,而global用于全局范围内的变量。

这就是nonlocal关键字的作用。但是还有一点值得注意,先看下面的例子。

def outside():    d = {"outside": 1}    def inside():        d["inside"] = 2        print(d)    inside()    print(d)


大家觉得输出是什么呢? 实际输出是这样的:

{'outside': 1, 'inside': 2}{'outside': 1, 'inside': 2}


原因嘛,当然是因为dict是可变对象了,但由于  d["inside"] = 2

 这样的语句是有点让人迷惑的,看起来很像重新赋值。实际上dict的赋值是调用了setitem方法。这样看就不会感到迷惑了。


# 下面的代码是等价的。d["inside"] = 2d.__setitem__("inside", 2)


关于nonlocal关键字,应该讲清楚了吧。 至于python的闭包,其实还是挺复杂的,上面的只是几个例子,想要更加深入的学习可以上stackoverflow上看看大佬们的回答。 很有学习的价值。

- END -