多语言展示
当前在线:1453今日阅读:60今日分享:41

面试题谈什么是Python闭包

详细介绍什么是 Python 闭包,从什么是闭包,闭包的定义,到手写一个简单的闭包、变量作用域规则与 nonlocal 关键字,闭包的应用四个方面回答该问题。
工具/原料
1

Python 3

2

Python 2

方法/步骤
1

首先,我们要了解变量作用域,如图中的代码所示。学过其他语言,比如 Java ,对示例三的结果会比较惊讶,在 Java 中类似的情况,不会报错,会引用外部的全局变量,而如果在内部重新赋值后,再次使用则会用局部变量的值。而在 Python 中情况则不一样,它在编译函数时,发现对 b 有赋值的操作,它判定 b 是一个局部变量,所以在打印 b 时,它会去查询局部变量b,发现并没有赋值,所以会抛出异常。

2

其实简单来说,Python 就是这样设计的,它认为在函数体中,如果对变量有赋值操作,则证明这个变量是一个局部变量,并且它只会从局部变量中去读取数据。这样设计可以避免我们在不知道的情况下,获取到全局变量的值,从而导致一些错误数据的出现。至于解决方法,就是使用 global 关键字,来说明我们使用的是 全局变量 。示例如图所示。

3

闭包的定义:简单来说,闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。有点绕,看图中的代码

4

nonlocal 关键字。上面的代码,有一个小缺陷,有很多重复的计算,当我们传入一个新的值想要得到新的平均值时,其他前一次的总和是可以通过外部临时变量存储的。于是我们很自然的想到下图中的代码:

5

这里报错的原因,请看第 1 点:变量的作用规则。因为 scores += val ,其实就是 scores = scores + val,有了赋值操作,则认为 scores 是局部变量了。而我们也没办法使用 global 关键字,因为此时 scores 和 count 是定义在 get_ave 函数内的,它们俩也是一个局部变量。而为什么我们使用 list 时,没有出现这个问题呢?也是很好理解的,因为我们使用的是 list.append() 方法,它没有赋值操作。你可以简单认为,可变对象(即我们可以通过调用自身一些方法去做增删改操作且变量地址值不变)不存在此问题,而不可变对象则会有。在 Python 3 中引入了一个关键词 nonlocal 解决了这一个问题:

6

你也许会说,那在 Python 2 的环境下应该怎么解决呢?恩,其实也是有办法的,思路就是将不可变对象变为可变对象即可。代码如下:

7

闭包的应用:首先是装饰器,装饰器就是通过修改被装饰函数,来达到增加新功能的作用。当我们在内部函数去修改被装饰函数时,大部分情况都会使用到闭包。简单示例:

8

另外一个应用由之前求平均值的示例也可以看出来,可以在重复计算时提高效率。其次还有一个比较重要的应用场景,就是利用“惰性求值”这一特性,这一点在 Django 的 QuerySet 里有体现。当我们利用 ORM 去做 SQL 查询时,很多时候会根据不同的判断条件,去加 filter,加 filter 的时候并没有真正做查询,在最终获取结果的时候才真正执行了查询。这一点有兴趣的可以去看下源码。

推荐信息