Jusene's Blog

理解python闭包概念

字数统计: 1.3k阅读时长: 5 min
2017/10/12 Share

闭包

如果在一个内部函数中,对外部作用域(但不是全局作用域)的变量进行引用,那么内部函数就被称为闭包,闭包是由函数及其相关的引用环境组合而成的实体(闭包=函数+引用环境),函数可以作为另一个函数的参数或返回值,可以赋值给一个变量,函数可以嵌套定义,即在一个函数内部再定义一个函数,有了嵌套函数这种结构,便产生了闭包。

闭包示例

1
>>> def counter(start_num):
2
...     num=start_num
3
...     def incr():
4
...             return num+1
5
...     return incr
6
>>> count=counter(10)
7
>>> count()
8
11
9
>>> count1=counter(20)
10
>>> count1()
11
21
12
>>> count()
13
11

这里内部函数incr的执行结果得到的相互隔离的,也就是说明在每次调用外部counter函数的时候会生成一个新的局部变量num,这里外部变量函数返回的就是闭包。

按照命令式语言的规则,外部函数只是返回了内嵌函数的地址,在执行内嵌函数时将会由于在其作用域内找不到外部变量而出错。而在函数式语言中,当内嵌函数体内引用到体外的变量时,将会把定义时涉及到的引用环境和函数体打包成一个整体(闭包)返回。由于闭包把函数和运行时的引用环境打包成一个新的整体,所以解决函数嵌套所引发的问题,如上述实例中,每次执行外部函数都返回一个新的闭包实例,这些实例是隔离的。

所以python中闭包的定义为:如果一个内部函数对外部函数作用域(不是全局作用域)的变量进行引用,那么内部函数就被认为是闭包。

闭包注意事项

  • 闭包不能修改外部作用域的局部变量
    1
    >>> def counter(start_num):
    2
    ...     num=start_num
    3
    ...     def incr():
    4
    ...				num+=1      #修改外部局部变量
    5
    ...             return num+1
    6
    ...     return incr
    7
    >>> count=counter(10)
    8
    >>> count()
    9
    Traceback (most recent call last):
    10
      File "<stdin>", line 1, in <module>
    11
      File "<stdin>", line 4, in incr
    12
    UnboundLocalError: local variable 'num' referenced before assignment
    分析下原因:当执行代码count=counter(10)的时候,外部的局部变量的闭包会被导入incr(),而python默认查找namespace的方式为local namespace->global namespace->build-in namespace来查找变量,而在闭包中“num=”等于在定义local namespace,所以python认为num是闭包中的局部变量,所以会出现异常num未定义。

python2中的解决方法,为num找一个容器。

1
>>> def counter(start_num):
2
...     num=[start_num]
3
...     def incr():
4
...             num[0]+=1
5
...             return num[0]
6
...     return incr
7
... 
8
>>> count=counter(10)
9
>>> count()
10
11
11
>>> count()
12
12

python3中更优雅的解决方法,显式指明num不是闭包的局部变量。

1
>>> def counter(start_num):
2
...     num=start_num
3
...     def incr():
4
...             nonlocal num
5
...             num+=1
6
...             return num
7
...     return incr
8
... 
9
>>> count=counter(10)
10
>>> count()
11
11
12
>>> count()
13
12
  • 闭包的陷阱

陷阱代码:

1
>>> def count():
2
...     fs=[]
3
...     for i in range(1,4):
4
...             def f():
5
...                     return i*i
6
...             fs.append(f)
7
...     return fs
8
... 
9
>>> f=count()
10
>>> f
11
[<function f at 0x109df7f50>, <function f at 0x109e02050>, <function f at 0x109e020c8>]
12
>>> print(f1(),f2(),f3())
13
(9, 9, 9)

我们都知道函数只会在调用的时候执行,而从上述的函数调用的过程中,首先执行了count(),而内部的函数还未执行,所以在整个执行过程中i的变量已经被循环成3了,所以当执行内部函数闭包时候传入的变量i应该都是3,所以执行为9是正确的。

我们验证下:

1
>>> def count():
2
...     fs=[]
3
...     for i in range(1,4):
4
...         def f():
5
...             return i*i
6
...         fs.append(f)
7
...     print(locals())
8
...     return fs
9
>>> c=count()
10
{'f': <function count.<locals>.f at 0x1053532f0>, 'fs': [<function count.<locals>.f at 0x105353510>, <function count.<locals>.f at 0x105353400>, <function count.<locals>.f at 0x1053532f0>], 'i': 3}

i确实是3,所以猜想是正确的。

解决方法:

1
>>> def count():
2
...     fs=[]
3
...     for i in range(1,4):
4
...             def f(y=i):
5
...                     return y*y
6
...             fs.append(f)
7
...     return fs
8
... 
9
>>> f=count()
10
>>> for i in f: print(i())
11
1
12
4
13
9

这里的结局思路就是,当闭包需要引入外部变量的时候,直接将外部的变量赋值到内部函数的局部变量即可以解决这种问题。

总结:

  1. 一个函数中,内部函数需要调用外部(非全局变量)的变量,我们就称这个内部函数是闭包
  2. 每次外部调用外部函数变量会形成新的闭包,两个闭包环境相互隔离
  3. 闭包不能修改外部作用域的局部变量
  4. 闭包想要修改外部的局部变量,python2需要找到一个容器,python3中可以显式告诉函数非闭包局部变量
  5. python函数只有在调用的时候才会执行

参考:http://www.cnblogs.com/JohnABC/p/4076855.html

CATALOG
  1. 1. 闭包
  2. 2. 闭包示例
  3. 3. 闭包注意事项
  4. 4. 总结: