嵌套函数

嵌套函数(Nested function)是在另一个函数(即:封闭函数)中定义的函数

那么,一般在什么情况下使用嵌套函数?

  • 封装 - 数据隐藏
  • 贯彻 DRY 原则
  • 闭包

封装 - 数据隐藏

可以使用内层函数来保护它们不受函数外部变化的影响,也就是说把它们从全局作用域隐藏起来。
来看一个简单的例子 - 求一个数字 n 的倍数:

def outer(n):
    def inner(n):
        return n * 2
    num = inner(n)
    print(num)

尝试调用 inner 会引发错误:

>>> outer(10) 
20  
>>> inner(10) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'inner' is not defined

这是因为它被定义在 outer() 的内部,被隐藏了起来,所以外部无法实现。

贯彻 DRY 原则

DRY(Don’t Repeat Yourself)- 是指在程序设计以及计算中避免重复代码,因为这样会降低灵活性、简洁性,并且有可能导致代码之间的矛盾。
DRY 更多的是一种架构设计思想,在软件开发过程中的万事万物均可能重复,大到标准、框架、开发流程;中到组件、接口;小到功能、代码均纯存在自我重复。而 DRY 提倡的就是在软件开发过程中应消除所有这些自我重复。

闭包

闭包是支持函数式编程范式的一个重要特性,在很多编程语言中都可以找到,包括:JavaScript、Python 和 Ruby。闭包十分强大,也非常有用,但是也很棘手,因为难以理解和使用。

关于闭包,来看维基百科上的词条:

在计算机科学中,闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

上面涉及一个关键点 - 自由变量,根据 Python 文档 描述:

如果在一个代码块中使用了一个变量,而这个变量并没有被定义在该代码块中,那么该变量就被称为自由变量。

产生闭包的条件

  • 必须包含一个嵌套函数
  • 嵌套函数必须引用封闭函数中定义的值(自由变量)
  • 封闭函数必须返回嵌套函数

用一个简单的程序来说明一下:

def outer(x):
    def inner():
        return x
    return inner

>>> f = outer("hello") 
>>> f()
'hello'

当外部函数 outer(x) 被调用时,一个闭包 inner() 就形成了,并且该闭包持有自由变量 x。这意味着,当函数 outer(x) 的生命周期结束之后,变量 x 的值依然会被记住。

>>> del outer
>>> f()
'hello'
>>>
>>> outer("hello") 
Traceback (most recent call last):
...
NameError: name 'outer' is not defined

可以看到,即使 outer 从当前的命名红箭中删除,x 的值 hello 也会被记住。

自由变量存放在呢?

所有的函数对象都有一个 __closure__ 属性,如果这个函数是一个闭包函数,那么会返回的是一个由 cell 对象组成的元组。cell 对象具有 cell_contents 属性,存储了闭包中的自由变量。

>>> f.__closure__
(<cell at 0x0000026133355B80: str object at 0x000002613337B530>,)
>>>
>>> f.__closure__[0].cell_contents
'hello'

这也就解释了为什么局部变量在脱离函数之后,还可以在函数之外被访问,因为它存储在了闭包的 cell_contents 中。

变量作用域

考虑下边的例子,每当调用函数时,为计数器加 1

def outer():
    count = 0
    def inner():
        count += 1
        return count
    return inner

看上去好像没有任何问题,但是很遗憾,执行时会引发错误:

>>> f = outer()
>>> f()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'count' referenced before assignment

这是因为 count 是一个不可变类型,当在内部范围对其重新分配时,它会被看作是一个新变量,由于它还没有被定义,所以会发生错误。

此时我们可以使用 nonlocal 关键字,用于标识外部作用域的变量。

def outer():
    count = 0
    def inner():
        nonlocal count
        count += 1
        return count
    return inner

再次运行,结果和预期一样:

>>> f = outer()
>>> f()
1
>>> f()
2
>>> f()
3