面试必问:嵌套函数、闭包与装饰器

书到用时方恨少,事非经过不知难。

—— 《增广贤文》

Python 函数可以嵌套定义,我们先考察一个典型的例子:

1
2
3
4
def adder(n):
    def handler(x):
        return n+x
    return handler

adder 函数负责创建处理函数 handler ,处理函数计算参数 x 与固定值 n 的和。因此,如果我们需要一个为参数加上 1 的函数,调用 adder(1) 即可轻松得到:

1
2
3
4
5
>>> add1 = adder(1)
>>> add1(10)
11
>>> add1(15)
16

同样,如果我们需要一个将参数加上 5 的函数,调用 adder(5) 即可:

1
2
3
4
5
>>> add5 = adder(5)
>>> add5(10)
15
>>> add5(15)
20

很显然,对于 add1 来说,n 的值是 1 ;对于 add5 来说,n 的值是 5 ;两者保存独立,互不干扰。

理论上,当函数 adder 返回,局部变量 n 应该就被回收了,为什么 handler 函数还能访问到它呢?

另外,像 adder 函数和 handler 函数这种嵌套写法,到底有什么作用?适用于什么开发场景?有什么需要特别注意的地方吗?

为了解答这诸多疑惑,我们需要深入学习 嵌套函数闭包 变量的来龙去脉。

嵌套函数

adder 函数和 handler 这样,在一个函数的函数体内定义另一个函数,就构成了 嵌套函数 。我们在 虚拟机 部分讲解 作用域 时,已对嵌套函数有所了解。你还记得嵌套函数与作用域之间密切的联系吗?

根据我们在虚拟机部分学到的知识,adder-handler 这段简单的代码却包含着 3 个不同的作用域:

作用域是一个静态概念,由 Python 代码语法决定,与编译后产生的 代码对象 一一对应。作用域规定了能够被某个代码块访问的变量有哪些,但对变量具体的值则一概不关心。

一旦 Python 程序开始运行,虚拟机需要为作用域中的变量分配一定的存储空间,这就是 名字空间 。名字空间依照作用域规则实现,它决定了某个变量在运行时的取值,可以看做是作用域在运行时的动态表现方式。

adder 函数执行时,作用域 A 在虚拟机中表现为 全局 名字空间,作用域 B 表现为 局部 名字空间:

handler 函数执行时,例如调用 adder(10) 时,作用域 A 在虚拟机中表现为 全局 名字空间,作用域 B 表现为 闭包 名字空间:作用域 C 表现为 局部 名字空间:

全局与局部这两个名字空间我们已经非常熟悉了,那么 闭包 名字空间又该如何理解呢?点击”阅读原文“,获取更多详情!

【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注:

【Python源码剖析】系列文章首发于公众号【小菜学编程】,敬请关注: