当前位置:首页>python>《Python 从入门到精通》099|闭包与作用域进阶:理解函数的高级能力

《Python 从入门到精通》099|闭包与作用域进阶:理解函数的高级能力

  • 2026-10-11 05:43:25
《Python 从入门到精通》099|闭包与作用域进阶:理解函数的高级能力

前一章我们刚刚讲完装饰器。很多人学装饰器时,会觉得有点神奇:

为什么一个函数能接收另一个函数 为什么函数里面还能再定义函数 为什么外层函数执行完了,里面那个包装函数居然还能继续用到外层的变量

如果你看到这里开始有点发愣,别慌。因为装饰器背后其实正好牵扯到这一章的核心内容:

作用域 闭包

这两个词一听就容易让人紧张。尤其是闭包,很多人第一次听,会本能觉得这是不是特别底层、特别高级、特别难。其实只要你把前面学过的函数、变量、嵌套函数这些东西重新串一下,它就没有那么吓人。

这一章我们就把这件事讲透。你会发现:

闭包不是什么遥远概念,它本质上就是“函数记住了它定义时周围的变量环境”。

而作用域,则是在回答另一个特别关键的问题:

一个变量,到底能在什么地方被访问。

这两件事一旦想明白,前面的装饰器会一下更通,后面的很多高级写法也会顺很多。

一、先从最基础的问题开始:变量到底在哪儿能用

你前面其实早就接触过作用域了,只是那时候还没有系统地叫这个名字。

比如:

name = '张三'defshow_name():    print(name)show_name()

这段代码能正常运行。 因为函数内部能访问外部的 name。

但如果你反过来这样写:

deftest():    age = 18print(age)

这就不行。 因为 age 是在函数内部定义的,它只在函数里面有效,函数外面拿不到。

你会发现,这里其实已经涉及作用域了。

所以先给一个很朴素的定义:

作用域,就是变量能被访问的范围。

这句话一定要先立住。 因为后面所有关于闭包、global、nonlocal 的问题,最后都绕回它。

二、先分清两种最基础的作用域

在你当前阶段,先把最常见的两种分清就够了:

全局作用域 局部作用域

全局作用域,就是在函数外定义的变量。 局部作用域,就是在函数内部定义的变量。

比如:

city = '北京'deftest():    score = 95    print(city)    print(score)test()

这里:

city 是全局变量score 是局部变量

函数内部为什么能打印 city

因为函数内部可以访问外层的全局变量。

那函数外部为什么不能打印 score

因为 score 属于局部变量,只在 test() 里面有效。

这就是最基础的作用域关系。

三、为什么作用域特别重要

因为它决定了你写代码时,变量到底是谁的。

比如一个变量是:

只给某个函数自己用 还是整个文件都能用 还是某个嵌套函数和外层函数共用

这些都不是小问题。

如果作用域意识不清,你写代码时就很容易出现这些情况:

变量名冲突 函数里莫名拿不到变量 修改变量时改错层级 明明看起来有变量,结果一运行就报错

所以作用域不是概念装饰品,而是变量管理规则本身。

四、先看一个最常见的坑:函数里为什么有时候能读外部变量,有时候又会报错

比如下面这个例子:

count = 10deftest():    print(count)test()

没问题,会输出:

10

但如果你写成这样:

count = 10deftest():    count = count + 1    print(count)test()

很多人会直觉觉得,这不是把全局的 count 加 1 吗。 其实这通常会报错。

为什么?

因为 Python 一看到你在函数内部给 count 赋值,就会认为:

你这里的 count 是局部变量。

于是这句:

count = count + 1

右边想先读取局部变量 count但这个局部变量此时还没赋值 所以就出问题了

这就是作用域里一个特别经典的坑。

也说明了一件事:

读取变量和修改变量,涉及的作用域判断并不完全一样。

五、这时候就要认识一个重要关键字:global

如果你真的想在函数内部修改全局变量,就要显式告诉 Python:

我这里用的不是新的局部变量 而是外面的全局变量

这时候就用:

global

例如:

count = 10deftest():global count    count = count + 1    print(count)test()print(count)

输出:

1111

这里的意思很明确:

函数里的 count,不是新建局部变量 而是去操作全局的那个 count

所以 global 的作用你可以先记成:

在函数内部声明:我要修改全局变量。

不过这里要提前提醒你:

能用,不代表应该乱用。 如果一个程序到处靠 global 改全局状态,后面很容易变乱。

所以当前阶段你要知道它是什么、为什么需要它,但不要养成“有问题就上 global”的习惯。

六、现在开始进入嵌套函数,这才是闭包真正要出现的地方

前面讲的还只是全局和局部。 但闭包真正精彩的地方,往往发生在函数套函数的时候。

比如:

defouter():    x = 10definner():        print(x)    inner()outer()

输出:

10

这里就很有意思了。

x 不是全局变量。 它是 outer() 的局部变量。 按理说,局部变量不是只能在自己函数里用吗?

但这里 inner() 居然也能访问到它。

这说明什么?

说明作用域不只是“全局”和“当前函数局部”这么简单。 在嵌套函数里,内层函数还可以访问外层函数的变量。

这就是理解闭包前,必须先建立起来的一层作用域关系。

七、现在可以引出一个非常经典的作用域顺序感了

你现在不用死背术语,但要先有一个顺序感:

当函数里使用一个变量时,Python 会先在当前函数内部找。 如果当前找不到,就往外层函数找。 再找不到,就去全局找。 还找不到,才会报错。

你可以先把这个理解成:

变量会一层一层往外找。

看这个例子:

name = '全局名字'defouter():    name = '外层名字'definner():        name = '内层名字'        print(name)    inner()outer()

输出:

内层名字

因为 inner() 先在自己这一层就找到了 name。

如果改成这样:

name = '全局名字'defouter():    name = '外层名字'definner():        print(name)    inner()outer()

输出:

外层名字

因为 inner() 自己没有 name,就去外层 outer() 找,找到了。

如果再改成:

name = '全局名字'defouter():definner():        print(name)    inner()outer()

输出:

全局名字

因为内层和外层都没有,就继续往全局找。

这套一层层往外找的感觉,一定要有。

八、那闭包到底是什么

现在终于可以给定义了。

先给最直白的版本:

闭包,就是内层函数记住并使用了外层函数变量的一种现象或结构。

这句话第一次看可能还是有点书面。我们把它翻译成人话。

闭包的核心味道是:

外层函数里有个变量 内层函数用到了它 而且外层函数执行完以后,这个变量居然还没消失 内层函数还能继续记住并使用它

这就叫闭包。

你可以先把它理解成:

函数把它出生环境里的某些变量,一起“带走了”。

这就是为什么闭包会让很多人一开始觉得有点神奇。 因为它打破了很多人对“函数执行完局部变量就没了”的直觉印象。

九、看一个最经典的闭包例子

defouter():    x = 10definner():        print(x)return innerf = outer()f()

输出:

10

这个例子特别关键,你一定要看懂。

执行 outer() 时,定义了变量 x = 10,然后定义了 inner(),最后返回 inner。

注意,返回的不是 inner() 的执行结果, 而是函数 inner 本身。

也就是说:

f = outer()

执行完以后,f 里拿到的是那个内层函数。

然后再调用:

f()

居然还能打印出 10。

为什么?

因为 inner 这个函数把外层的 x 记住了。 即使 outer() 已经执行完了,这个 x 依然被保留下来,供 inner() 使用。

这,就是闭包最标准、最核心的例子。

十、这件事为什么会让很多人第一次觉得神奇

因为按普通直觉你会觉得:

outer() 都执行完了 它里面的局部变量 x 不是应该没了吗 为什么后面 f() 还能拿到它

这正是闭包的关键价值:

只要内层函数还在引用外层变量,这个变量就不会按普通局部变量那样立刻消失。

你可以把它想成:

原本 x 应该随着 outer() 结束而退场 但因为 inner() 还在用它 所以 Python 把它保留下来了

这就像一个函数把它需要的外部资料打包带走了。 以后即使离开了原来的环境,它还是能继续用这些资料。

这个“记住环境”的能力,就是闭包最迷人的地方。

十一、闭包最常见的实际用途之一:做一个“带状态的函数”

看这个例子:

defmake_counter():    count = 0defcounter():        print(count)return counter

这个版本虽然能访问 count,但还不能修改。 如果想让它真正像计数器,就会碰到一个问题:

内层函数如果想修改外层函数变量,不能直接像全局变量那样用 global。 因为这不是全局变量,而是“外层函数变量”。

这时候,就要认识另一个关键字:

nonlocal

十二、nonlocal 是干什么的

你可以先记一句最实用的话:

nonlocal 用来在内层函数中声明:我要修改外层函数的变量。

注意,它不是改全局变量。 它是改“最近那层外部函数里的变量”。

比如:

defmake_counter():    count = 0defcounter():nonlocal count        count += 1        print(count)return counterc = make_counter()c()c()c()

输出:

123

这里就特别有感觉了。

第一次调用 c(),输出 1。 第二次调用,输出 2。 第三次调用,输出 3。

这说明什么?

说明那个外层变量 count 没有随着第一次调用后就重置掉。 它被保存在闭包环境里了。

这就是闭包特别典型的实际价值:

它可以让函数带着自己的状态活下去。

十三、global 和 nonlocal 的区别一定要分清

这两个词很容易混。

global是在函数内部声明:我要修改全局变量

nonlocal是在内层函数内部声明:我要修改外层函数的变量

看两个例子对比一下。

全局变量修改:

x = 10deftest():global x    x += 1

外层函数变量修改:

defouter():    x = 10definner():nonlocal x        x += 1

你可以这样记:

全局那一层,用 global嵌套函数往外那一层,用 nonlocal

只要这个层级感清楚,就不容易混。

十四、为什么闭包和装饰器关系特别深

因为装饰器底层几乎天然就在用闭包。

比如我们前一章写过:

defdecorator(func):defwrapper():        print('函数开始执行')        func()return wrapper

这里的 wrapper() 为什么能在后面继续调用 func?

因为 wrapper 把外层 decorator(func) 里的 func 记住了。

也就是说,wrapper 本身就是一个闭包。 它在外层函数已经执行完后,依然能使用外层传进来的 func。

所以如果你前一章学装饰器觉得还有一点朦胧,这一章应该会突然通很多:

装饰器之所以能工作,很大程度上正是因为闭包让包装函数记住了原函数。

这就是为什么目录里这两章会紧挨着。

十五、再看一个特别接近装饰器本质的例子

defouter(msg):definner():        print(msg)return innerf1 = outer('你好')f2 = outer('欢迎学习 Python')f1()f2()

输出:

你好欢迎学习 Python

这里特别有意思。

f1 和 f2 都是 inner 这种结构出来的函数。 但它们各自记住了不同的 msg。

这就说明,闭包不是简单“共享一个外层变量”, 而是每次创建时,都可以记住当时那份独立环境。

你会发现,这已经特别像工厂模式了:

给我一个参数 我返回一个“带着这个参数记忆”的函数

这就是闭包很强大的地方。

十六、闭包为什么特别适合做“定制函数”

因为它能把参数和行为打包在一起。

比如你想做一个“加法器工厂”:

defmake_adder(n):defadder(x):return x + nreturn adderadd_5 = make_adder(5)add_10 = make_adder(10)print(add_5(3))print(add_10(3))

输出:

813

这里你会发现:

add_5 永远记住了 n = 5add_10 永远记住了 n = 10

这其实已经不是简单函数调用了, 而是在“造函数”。

这种“定制函数”的能力,在很多进阶写法里都会出现。

十七、为什么说闭包是函数高级能力的体现

因为到了闭包这里,函数已经不只是“接收参数、返回结果”那么简单了。

它开始拥有下面这些高级特征:

函数可以作为返回值返回 函数可以记住创建时的环境 函数可以带着状态继续活着 函数可以被工厂化地批量生成 函数可以在不暴露内部变量的情况下持续工作

这说明函数在 Python 里并不只是一个“代码片段”, 而是一个真正的一等对象,并且具备很强的表达力。

所以这章的价值,不只是学会闭包本身, 更重要的是你会开始真正体会:

函数在 Python 里,比很多初学者一开始理解得要强大得多。

十八、一个很常见的误区:把闭包当成必须刻意追求的高级技巧

其实不是。

闭包当然重要, 但它不是那种“我写啥都得硬套进去”的东西。

真正成熟的理解应该是:

当你需要一个函数记住某些外部状态 或者需要用函数工厂定制行为 或者你在读装饰器、回调、框架代码时看到它 你要能认出来,能理解,能适度使用

这就够了。

而不是为了显得懂高级语法,平时什么小逻辑都包成闭包。 那反而会让代码变复杂。

所以闭包要学透,但不要滥用。

十九、本章小练习

你可以做三个特别适合巩固的练习。

练习 1 写一个外层函数,接收一个字符串参数,返回一个内层函数。 内层函数调用时打印这个字符串。

练习 2 写一个 make_multiplier(n),返回一个函数。 这个返回的函数接收一个数字 x,返回 x * n。

练习 3 写一个简单计数器,用 nonlocal 让每次调用都加 1 并打印结果。

参考思路如下:

defouter(msg):definner():        print(msg)return innerf = outer('你好')f()defmake_multiplier(n):defmultiplier(x):return x * nreturn multipliertimes_3 = make_multiplier(3)print(times_3(5))defmake_counter():    count = 0defcounter():nonlocal count        count += 1        print(count)return counterc = make_counter()c()c()c()

只要你把这几个例子亲手敲一遍,这一章的主干就会非常稳。

二十、本章总结

这一章最重要的,是把作用域和闭包真正串起来理解。

作用域,就是变量能被访问的范围。 最基础的有全局作用域和局部作用域,而嵌套函数还会引出更细的外层函数作用域。 函数内部读取变量时,会一层层往外找。global 用来在函数内部声明要修改全局变量。nonlocal 用来在内层函数中声明要修改外层函数变量。 闭包的核心,是内层函数记住并使用了外层函数的变量环境。 这也是为什么外层函数执行完以后,内层函数仍然能继续访问那些变量。 装饰器之所以成立,很大程度上正是因为闭包让包装函数记住了原函数。 闭包最典型的价值,是做带状态的函数、函数工厂和定制行为。

下一章我们继续往前走,把这一整个阶段真正收束起来:100|高级语法总结:从“会写”走向“写得漂亮”。

最新文章

随机文章