返回函数,顾名思义,就是高阶函数能够把函数做为return值返回。与闭包的关系是:闭包须要以返回函数的形式实现。python
一. 返回函数编程
好比咱们有一个求和函数:闭包
>>> def calc_sum(num_list): s = 0 for i in num_list: s += i return s >>> calc_sum([1,2,3,4]) 10
当咱们不须要马上求和,而是后面根据须要再计算结果时,咱们能够返回求和的函数,而不是直接返回计算结果。这就是返回函数。app
>>> def lazy_calc_sum(num_list): def calc_sum(): s = 0 for i in num_list: s += i return s return calc_sum >>> f_lazy = lazy_calc_sum([1,2,3,4]) >>> f_lazy <function lazy_calc_sum.<locals>.calc_sum at 0x0000003A8D92E9D8> >>> f_lazy() 10
很显然,这样能让咱们根据需求,节省计算资源。函数式编程
二. 闭包函数
在上面的例子中,咱们在函数lazy_clac_sum
中又定义了函数calc_sum
,而且,内部函数calc_sum
能够引用外部函数lazy_calc_sum
的参数和局部变量,当lazy_calc_sum
返回函数calc_sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”。spa
若是让定义更加清晰一些: 若是在一个内部函数里对在外部做用域(但不是在全局做用域)的变量进行引用,但不在全局做用域里,则这个内部函数就是一个闭包。code
实际上,闭包的用处/优势有两条:对象
下面例子是,咱们建立了一个下载download函数,而后下载次数一直存储在内存中。blog
>>> def download_enter(download_times): def download():
nonlocal download_times download_times += 1 print("This is the %s time download" % download_times) return download >>> >>> d = download_enter(0) >>> d() This is the 1 time download >>> d() This is the 2 time download >>> d() This is the 3 time download
下面的例子是闭包根据外部做用域的局部变量来获得不一样的结果,这有点像一种相似配置功能的做用,咱们能够修改外部的变量,闭包根据这个变量展示出不一样的功能。好比有时咱们须要对某些文件的特殊行进行分析,先要提取出这些特殊行。
def make_filter(keep): def the_filter(file_name): file = open(file_name) lines = file.readlines() file.close() filter_doc = [i for i in lines if keep in i] return filter_doc return the_filter
若是咱们须要取得文件"result.txt"中含有"pass"关键字的行,则能够这样使用例子程序
filter = make_filter("pass") filter_result = filter("result.txt")
以上两种使用场景,用面向对象也是能够很简单的实现的,可是在用Python进行函数式编程时,闭包对数据的持久化以及按配置产生不一样的功能,是颇有帮助的。
关于闭包的2个常见错误:
1. 尝试在闭包中改变外部做用域的局部变量
def foo(): a = 1 def bar(): a = a + 1 return a return bar
>>> c = foo() >>> print c() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in bar UnboundLocalError: local variable 'a' referenced before assignment
这段程序的本意是要经过在每次调用闭包函数时都对变量a进行递增的操做。但在实际使用时,a = a + 1的a会被python解释器认为是bar()函数的局部变量,从而引发“referenced before assignment”的错误。
解决方法有两个:
方法一:将a设置为一个容器,好比列表List (不推荐)
方法二:将a声明为nonlocal变量(仅在Python3支持),这样声明事后,就不会被认为是bar()函数的局部变量,而是会到上一层函数环境中寻找这个变量。
下面是例子:
>>> def foo(): a = 1 b = [1] def bar(): nonlocal a a = a + 1 b[0] = b[0] + 1 return a,b[0] return bar >>> c = foo() >>> print(c()) (2, 2) >>> print(c()) (3, 3) >>> print(c()) (4, 4)
2. 误觉得返回的函数就已执行,对执行结果误判
直接举例子说明:
def count(): fs = [] for i in range(1, 4): def f(): return i*i fs.append(f) return fs f1, f2, f3 = count()
在上面的例子中,每次循环,都建立了一个新的函数,而后,把建立的3个函数都返回了。
你可能认为调用f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:
>>> f1() 9 >>> f2() 9 >>> f3() 9
所有都是9
!缘由就在于返回的函数引用了变量i
,但它并不是马上执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3
,所以最终结果为9
。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
若是必定要引用循环变量怎么办?方法是再建立一个函数,用该函数的参数绑定循环变量当前的值,不管该循环变量后续如何更改,已绑定到函数参数的值不变:
def count(): def f(j): def g(): return j*j return g fs = [] for i in range(1, 4): fs.append(f(i)) # f(i)马上被执行,所以i的当前值被传入f() return fs
结果是:
>>> f1, f2, f3 = count() >>> f1() 1 >>> f2() 4 >>> f3() 9