根据Python常见面试题来谈谈 Python 闭包
这里根据我们平常见到的一些Python常见面试题来跟大家说一下关于Python闭包的相关问题!
题目:
1.什么是闭包?闭包的定义?
2.手写一个简单的闭包
3.变量作用域规则与 nonlocal 关键字?
4.闭包的应用
答案要点:
1.首先,我们要了解变量作用域
#示例一deftest1(a):print(a)print(b)#当函数执行到这一步时会报错。#NameError:globalname'b'isnotdefinedtest1(1)#示例二b=6deftest2(a):print(a)print(b)test2(1)#16#示例三b=6deftest3(a):print(a)print(b)#当函数执行到这一步仍然会报错#UnboundLocalError:localvariable'b'referencedbeforeassignmentb=9#比示例二多了一行赋值test3(1)
学过其他语言,比如 Java ,对示例三的结果会比较惊讶,在 Java 中类似的情况,不会报错,会引用外部的全局变量,而如果在内部重新赋值后,再次使用则会用局部变量的值。而在 Python 中情况则不一样,它在编译函数时,发现对 b 有赋值的操作,它判定 b 是一个局部变量,所以在打印 b 时,它会去查询局部变量b,发现并没有赋值,所以会抛出异常。
引用《流畅的Python》中对此的解释:
这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。这比 JavaScript 的行为要好多了,JavaScript 也不要求声明变量,但是如果忘记把变量声明为局部变量(使用var),可能会在不知情的情况下获取全局部变量。
上段话第一次看可能会有点不明白,其实简单来说,Python 就是这样设计的,它认为在函数体中,如果对变量有赋值操作,则证明这个变量是一个局部变量,并且它只会从局部变量中去读取数据。这样设计可以避免我们在不知道的情况下,获取到全局变量的值,从而导致一些错误数据的出现。
至于解决方法,就是使用 global 关键字,来说明我们使用的是 全局变量 。示例如下:
b=6deftest4(a):print(a)globalb#1print(b)#6b=9print(b)#9test4(1)
2.闭包的定义:简单来说,闭包的概念就是当我们在函数内定义一个函数时,这个内部函数使用了外部函数的临时变量,且外部函数的返回值是内部函数的引用时,我们称之为闭包。有点绕
代码如下:
#一个简单的实现计算平均值的代码defget_avg():scores=[]#外部临时变量definner_count_avg(val):#内部函数,用于计算平均值scores.append(val)#使用外部函数的临时变量returnsum(scores)/len(scores)#返回计算出的平均值returninner_count_avg#外部函数返回内部函数引用avg=get_avg()print(avg(10))#10print(avg(11))#10.5...
3.nonlocal 关键字。上面的代码,有一个小缺陷,有很多重复的计算,当我们传入一个新的值想要得到新的平均值时,其他前一次的总和是可以通过外部临时变量存储的。于是我们很自然的想到下面的代码:
#一个简单的实现计算平均值的代码改进版一defget_avg():scores=0#将外部临时变量由list改为一个整型数值count=0#同时新增一个变量,记录个数definner_count_avg(val):#内部函数,用于计算平均值scores+=val#使用外部函数的临时变量count+=1returnscores/count#返回计算出的平均值returninner_count_avg#外部函数返回内部函数引用avg=get_avg()print(avg(10))#报错...
这里报错的原因,请看第 1 点:变量的作用规则。因为 scores += val ,其实就是 scores = scores + val,有了赋值操作,则认为 scores 是局部变量了。而我们也没办法使用 global 关键字,因为此时 scores 和 count 是定义在 get_ave 函数内的,它们俩也是一个局部变量。而为什么我们使用 list 时,没有出现这个问题呢?也是很好理解的,因为我们使用的是 list.append() 方法,它没有赋值操作。你可以简单认为,可变对象(即我们可以通过调用自身一些方法去做增删改操作且变量地址值不变)不存在此问题,而不可变对象则会有。
在 Python 3 中引入了一个关键词 nonlocal 解决了这一个问题:
#一个简单的实现计算平均值的代码改进版二defget_avg():scores=0#将外部临时变量由list改为一个整型数值count=0#同时新增一个变量,记录个数definner_count_avg(val):#内部函数,用于计算平均值nonlocalcount,scoresscores+=val#使用外部函数的临时变量count+=1returnscores/count#返回计算出的平均值returninner_count_avg#外部函数返回内部函数引用avg=get_avg()print(avg(10))#报错
你也许会说,那在 Python 2 的环境下应该怎么解决呢?emm~其实呢 也是有办法的,思路就是将不可变对象变为可变对象即可。具体代码如下:
#-*-coding:utf-8-*-classScore:passdefget_avg():s=Score()#使用类对象s.scores=0.0#注意Python2中整数除法是舍弃小数的,所以要定义为浮点数s.count=0definner_get_avg(val):s.count+=1s.scores+=valreturns.scores/s.countreturninner_get_avgavg=get_avg()print(avg(10))#10.0print(avg(11))#10.5
4.闭包的应用:首先是装饰器,装饰器就是通过修改被装饰函数,来达到增加新功能的作用。当我们在内部函数去修改被装饰函数时,大部分情况都会使用到闭包。简单示例:
defdecorator(func):#外部函数的局部变量funcdefwrapper(*args,**kwargs):#接受被包装函数传入过来的参数returnfunc(*args,**kwargs)#使用外部函数的局部变量funcreturnwrapper@decoratordefbasic_func(name):print'mynameis',name#等价于decorator_func(func)
另外一个应用由之前求平均值的示例也可以看出来,可以在重复计算时提高效率。其次还有一个比较重要的应用场景,就是利用“惰性求值”这一特性,这一点在 Django 的 QuerySet 里有体现。当我们利用 ORM 去做 SQL 查询时,很多时候会根据不同的判断条件,去加 filter,加 filter 的时候并没有真正做查询,在最终获取结果的时候才真正执行了查询。
这里也是根据我们平常见到的一些Python常见面试题来跟大家讲的Python闭包,有补充的伙伴非常欢迎留言补充哈!也希望大家能够互相学习,取长补短,进步呀!
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。