学函数的时候,很多人前面几章都觉得还行。
定义函数会了 参数会传了 返回值也知道怎么接了*args、**kwargs 也开始有点感觉了
可一旦写到稍微复杂一点的函数,很多人就会突然撞上一类特别经典的问题:
为什么这个变量在外面能用,进了函数就不认了 为什么函数里面改了变量,外面却没变 为什么有时候能直接访问外面的变量,有时候又报错 为什么明明变量名一样,结果程序却像在说两回事
这些问题背后,几乎都绕不开一个关键词:
作用域
这两个字听起来有点抽象。 但你只要把它理解成:
一个变量到底能在什么范围内被看见、被使用
事情就会清楚很多。
这一章,我们就把函数里的作用域彻底讲明白。 尤其是两个最容易绕晕人的角色:
局部变量 全局变量
你会发现,很多看起来像玄学的 bug,其实都只是因为变量活跃的范围没搞清楚。
一、先说结论:不是所有变量都到处都能用
很多新手刚开始写程序时,容易有一种直觉:
我前面定义过变量,那后面应该都能用吧
这在前面简单代码里,有时确实看起来像这样。 可一旦进了函数,规则就变了。
看这个例子:
defshow_info(): name = '张三' print(name)show_info()print(name)
你可能以为输出会是两次 张三。 其实不是。
第一句在函数里面,确实能打印。 但后面那句 print(name) 会直接报错。
为什么?
因为这个 name 是在函数内部定义的。 它不是全程序通用变量。 它只在函数内部有效。
这就是作用域的第一层真相:
变量不是你定义出来就天下通用 它的有效范围,是有限制的
二、什么是作用域
你可以先用一句最直白的话理解:
作用域,就是变量能生效的范围
比如有的变量只在函数内部能用。 有的变量在整个文件里都能用。 有的变量在循环里、判断里虽然也能访问,但它们的规则又和函数不完全一样。
当前这一章,我们先只抓最核心、最常见的函数场景。
也就是:
函数里面定义的变量,和函数外面定义的变量,到底是什么关系
你把这一层真正吃透,后面很多问题都会顺很多。
三、什么是局部变量
局部变量,就是:
在函数内部定义的变量 它通常只在这个函数内部有效
看最基础的例子:
defgreet(): message = '你好,欢迎学习 Python' print(message)greet()
输出:
你好,欢迎学习 Python
这里的 message,就是局部变量。
因为它是在 greet() 函数内部定义的。 所以它的活动范围,基本也只限于这个函数里面。
你可以把它理解成:
这是函数自己的私人物品 在这个函数屋子里可以随便用 一走出屋子,外面的人就不认了
比如:
defgreet(): message = '你好' print(message)greet()print(message)
这里最后那句就会报错。 原因不是变量没创建过,而是它只活在函数内部。
这就是局部变量最核心的特点:
只在局部范围内有效
四、局部变量为什么会存在
这个问题特别值得想一下。
如果程序里所有变量都全局通用,会怎么样?
一开始看着好像方便。 可代码一多,就会特别混乱。
比如你写一个函数处理学生成绩,里面用了变量 score。 另一个函数处理商品价格,也用了变量 score。 如果这些变量全都混在同一个大池子里,程序很快就会互相干扰。
而局部变量的价值就在于:
把函数内部的细节,关在函数自己的范围里
这个函数里定义什么变量, 通常不会轻易影响到外面。 外面定义什么变量, 函数内部也可以有自己的独立处理空间。
所以局部变量不是故意给你添规则。 它是在帮程序建立隔离感。
这对代码清晰度和安全性都特别重要。
五、什么是全局变量
全局变量,就是:
在函数外部定义的变量 它通常在当前整个文件范围内都能被访问
比如:
name = '张三'defshow_name(): print(name)show_name()print(name)
输出:
张三张三
这里的 name 是在函数外面定义的。 所以它属于全局变量。
函数内部可以访问它。 函数外部当然也能访问它。
你可以把它理解成:
这是公共区域里的东西 大家都能看见
所以局部变量像函数自己的私人物品, 全局变量更像公共大厅里的公告板。
六、为什么函数里有时候能直接用外面的变量
很多人第一次学到这里会有点懵。
前面明明说函数内部变量有自己的范围。 那为什么有时候函数里又能直接访问函数外部定义的变量?
比如:
city = '北京'defshow_city(): print(city)show_city()
这能正常输出。
原因是:
函数内部可以读取外层作用域里的变量 只要它自己内部没有同名变量把它挡住
也就是说,Python 在函数里找变量时,通常会先看函数内部有没有。 如果没有,再往外找。
所以你会看到一种现象:
函数内部可以“借用”外面的全局变量 但这不等于函数内部和外部变量完全是一回事
这个区别后面马上就会体现出来。
七、局部变量和全局变量同名时,会发生什么
这是一个特别经典、也特别容易绕晕人的点。
看代码:
name = '全局张三'defshow_name(): name = '局部李四' print(name)show_name()print(name)
输出:
局部李四全局张三
你会发现:
函数里面打印的是 局部李四 函数外面打印的是 全局张三
这说明什么?
说明函数内部那个 name,并没有改掉外面的 name。 它只是自己又创建了一个同名的局部变量。
也就是说:
同名时,函数内部优先使用自己的局部变量
这就像教室里有一张写着 name 的小纸条,公共大厅里也有一张写着 name 的大公告。 当你在教室里找 name,会先看到教室里的这张,不会先跑去大厅看。
所以以后你看到函数里变量名和外面一样,先别默认它们是同一个东西。 很可能只是同名而已,实际不是一份数据。
八、为什么很多人会误以为“函数里改了,全局也会跟着变”
因为名字一样,看起来太像了。
比如:
count = 0defchange(): count = 100 print(count)change()print(count)
很多人会以为输出都是 100。 但其实结果是:
1000
函数里的 count = 100,并没有改全局变量。 它只是定义了一个新的局部 count
这就是作用域最容易让人误判的地方:
你看见的是同一个名字 但 Python 看见的是两个不同作用域里的两个变量
所以以后只要函数内部出现了赋值语句,你就要格外警惕。 因为这很可能意味着:
你不是在用外面的变量 而是在函数内部创建了一个新的局部变量
九、一个更容易踩坑的例子:为什么这里会报错
看这段代码:
count = 0defadd_one(): count = count + 1 print(count)add_one()
很多人会觉得:
我只是想在原来的 0 基础上加 1,有什么问题吗
可这段代码通常会直接报错。
为什么?
因为 Python 看到函数内部有:
count = ...
就会先判断:
哦,这个 count 是函数里的局部变量
那问题来了。 你右边又写了:
count + 1
可此时这个局部变量 count 还没真正有值。 于是程序就会很困惑:
你要用的这个 count,到底是谁? 局部的还没准备好,外面的又被你这个赋值动作给遮住了。
这就是为什么这种写法特别容易报错。
这个坑非常经典,几乎是学作用域时的必踩点之一。
你现在不用死抠报错原理的每个字。 先记住最核心的现象:
函数内部一旦对某个变量赋值,Python 往往就会把它当成局部变量看待
这会直接影响你后面读写变量的结果。
十、那如果我真的想在函数里改全局变量,怎么办
这时候就要用到 global。
看例子:
count = 0defadd_one():global count count = count + 1 print(count)add_one()print(count)
输出:
11
这里的 global count 的意思是:
我接下来在这个函数里用到的 count,不是新建局部变量 我要明确操作外面的那个全局变量
于是:
函数里改了 外面也真的跟着变了
这就是 global 最直接的用途。
十一、global 到底该怎么理解
你可以先把它理解成一句声明:
我现在要告诉 Python 这个变量别按局部的算 我要用全局那份
比如:
total = 100defchange_total():global total total = 200
这里就是在明确说:
函数里的 total,指向外面的那个全局变量 我要改的是它,不是新建一个局部同名变量
所以 global 的核心,不是创造变量, 而是打破默认的局部解释方式,明确去操作全局那份。
这也是为什么它看起来有点“强力”。 因为它是在突破函数本来的边界感。
十二、能用 global,就代表应该多用吗
不代表。
这一点很重要。
虽然 global 能解决“函数里改全局变量”的问题, 但它并不是一种应该随手乱用的好习惯。
为什么?
因为一旦函数到处修改全局变量,程序会变得很难追踪。
你在外面看到一个变量值变了, 却不知道到底是哪个函数、哪一段逻辑改了它。 代码一复杂,这种问题会特别难调。
所以更稳的思路通常是:
函数尽量通过参数接收输入 通过返回值交回结果 而不是频繁直接改全局变量
也就是说:
global 你要会 但别把它当成默认方案
它更像一个在特定场景下才动用的工具,而不是日常首选。
十三、什么时候全局变量看起来很方便,实际上却很危险
比如你写一个小脚本时,很容易这样干:
total = 0defadd_money(x):global total total += xadd_money(100)add_money(50)print(total)
这当然能跑。
可一旦程序稍微大一点, 有好几个函数都能改 total, 你就会很难看清这个变量到底在哪些地方被动过手脚。
这会让程序越来越难维护。
所以全局变量不是不能用, 但你要有基本警惕:
它很方便 但也很容易让程序状态变得不透明
尤其是初学阶段,很多人一遇到传参数麻烦,就想走全局变量捷径。 这其实很容易把后面的函数逻辑搞乱。
十四、局部变量最大的好处,其实是“自带隔离”
这一点非常值得理解。
比如两个函数都想用一个叫 temp 的变量:
deffunc1(): temp = 10 print(temp)deffunc2(): temp = 20 print(temp)func1()func2()
输出:
1020
它们互不干扰。
这就是局部变量的价值。 每个函数都像一个相对独立的小房间。 房间里放的临时变量,只在自己屋里起作用,不会轻易跑出去撞别人。
如果没有这种隔离,程序会乱得非常快。
所以很多时候,局部变量不是“受限制”, 而是“被保护”。
它在保护函数的独立性,也在保护程序的可维护性。
十五、为什么函数更推荐“参数 + 返回值”,而不是“全局变量 + 修改”
因为前者更清楚,后者更隐蔽。
比如这两种写法:
第一种,靠全局变量:
score = 60defadd_score():global score score += 10
第二种,靠参数和返回值:
defadd_score(score):return score + 10
你会发现,第二种明显更清晰。
你一眼就能看出:
输入是什么 输出是什么 函数内部不会偷偷改外面环境
这也是为什么前面一直强调,函数最健康的工作方式往往是:
接收参数 内部处理 返回结果
而不是到处直接动全局状态。
这不是语法洁癖, 而是因为这样的代码更稳、更容易追踪、更容易复用。
十六、一个特别典型的实战例子:清洗文本时变量为什么互不影响
比如:
text = ' PYTHON 'defclean_text(): text = ' hello ' text = text.strip().lower() print(text)clean_text()print(text)
输出:
hello PYTHON
这里函数里的 text 和外面的 text 看起来同名, 但其实完全不是同一个变量。
函数内部清洗的是自己的局部变量。 外面的全局 text 根本没被动到。
这个例子特别适合帮助你建立一个意识:
同名不等于同一个变量 关键要看它属于哪个作用域
十七、再看一个更像业务逻辑的例子
比如统计订单数量:
order_count = 0defcreate_order():global order_count order_count += 1 print(f'当前订单数:{order_count}')create_order()create_order()print(order_count)
这当然能跑。 输出也会正常增加。
但如果你从代码可维护性角度看,就会发现:
create_order() 这个函数不是纯粹的输入输出型函数 它在悄悄改外部状态
这在真实项目里就会带来一个问题:
你调用一次函数,不只是“创建订单”这么简单 它还会顺手修改全局计数器
这种副作用一多,程序就会越来越难追踪。
所以这类例子其实刚好说明:
global 有时候确实能解决问题 但如果能通过更清晰的设计避开它,通常会更好
十八、什么是“副作用”
你现在不用把这个术语背得太重。 简单理解就行。
函数的副作用,粗略来说就是:
函数除了完成表面功能,还顺手影响了外部世界
比如:
改了全局变量 写了文件 改了外部列表内容 输出了日志 发了网络请求
当前这一章你只要先盯住一种最典型的副作用:
修改全局变量
因为它是新手阶段最容易踩坑的地方。
以后你会慢慢体会到, 函数越是隐蔽地影响外部状态,程序就越难维护。
这也是为什么很多时候局部变量和返回值更被推荐。
十九、作用域问题,最容易在调试时暴露出来
很多人平时写函数没觉得有问题。 一旦程序结果不对,就开始找半天。
后来才发现:
原来不是逻辑错了 而是变量根本不是你以为的那一份
比如你以为函数改的是外面那个 count, 其实它改的是自己新建的局部 count
或者你以为函数里面直接能用某个变量, 结果那个变量其实根本不在它能访问的范围里
所以作用域这个知识点表面上是规则, 实际上和调试能力关系特别大。
很多变量 bug,不是运算错了, 而是变量归属搞错了。
二十、一个特别实用的判断方法:先问自己,这个变量是谁家的
以后你在函数里看到一个变量,可以先问自己两个问题:
它是在哪里定义的 我现在是在读它,还是在改它
这两个问题特别关键。
如果它是在函数内部定义的,那大概率是局部变量。 如果它是在函数外定义的,那大概率是全局变量。
如果你只是在函数里读取外面的全局变量,通常问题不大。 但如果你在函数里对这个变量赋值,那情况就会复杂很多。 这时候你就要非常警惕:
Python 可能已经把它当成局部变量了 除非你明确用 global 声明
这个判断流程非常实用。 很多初学者 bug,其实就卡在这两步没想清楚。
二十一、局部变量和全局变量,到底该优先依赖谁
答案通常是:
优先依赖局部变量、参数和返回值 少依赖全局变量 更少依赖 global 去修改全局变量
为什么?
因为局部变量的边界更清楚。 参数和返回值的流向更清晰。 而全局变量越多,程序耦合就越重。
你现在可能还没写到特别大的程序。 但这个习惯越早建立越好。
因为后面程序一大, 靠全局变量硬撑的代码通常都会越来越难受。
二十二、最容易犯的几个错
第一个错,误以为函数内部定义的变量,外面也能直接用。
第二个错,看到函数里和外面变量同名,就误以为它们一定是同一个。
第三个错,想在函数里修改全局变量,却忘了 global,结果不是报错,就是逻辑不对。
第四个错,是一遇到传参麻烦,就开始大量上全局变量。 短期省事,长期很容易让代码失控。
第五个错,是把读取全局变量和修改全局变量混为一谈。 这两件事的规则并不一样。
这个区分一定要清楚:
函数里读全局变量,通常比较自然 函数里改全局变量,要格外小心
二十三、一个特别值得记住的小结论
你可以先把这一章最实用的一层总结成三句话。
函数内部定义的变量,通常是局部变量。 函数外部定义的变量,通常是全局变量。 函数里如果要修改全局变量,通常需要 global。
这三句并不覆盖所有更深层的作用域细节, 但对当前阶段已经非常够用了。
你先把这三层站稳, 后面再往更复杂的闭包、嵌套作用域、nonlocal 那些地方走,会轻松很多。
二十四、练习题:这一章一定要自己敲
下面这些小练习非常关键,建议你手动敲一遍。
1. 观察局部变量只能在函数内部使用
deftest(): x = 10 print(x)test()
然后试着在函数外面加一句:
print(x)
看看会发生什么。
2. 观察函数内部和外部同名变量互不影响
name = '全局张三'deftest(): name = '局部李四' print(name)test()print(name)
3. 观察函数内部读取全局变量
city = '北京'deftest(): print(city)test()
4. 观察函数内部修改全局变量时的情况
先试试不加 global:
count = 0deftest(): count = 100 print(count)test()print(count)
再试试加上 global:
count = 0deftest():global count count = 100 print(count)test()print(count)
你会非常直观地看到区别。
5. 尝试把“修改全局变量”的写法,改成“参数 + 返回值”写法
比如:
defadd_one(count):return count + 1count = 0count = add_one(count)print(count)
你会慢慢体会到,为什么这种方式通常更清楚。
二十五、本章小结
这一章我们把函数里最容易绕晕人的变量范围问题讲透了。
局部变量,是函数内部定义的变量,通常只在函数内部有效。 全局变量,是函数外部定义的变量,通常整个文件里都能访问。 函数内部可以读取全局变量,但一旦在函数里对同名变量赋值,Python 往往会把它当成局部变量。 如果你真的想在函数里修改全局变量,通常需要用 global 明确声明。
更重要的是,你现在应该开始建立一种更健康的函数习惯:
尽量用参数接收输入 用返回值交回结果 少依赖全局变量 更少依赖 global 去偷偷改全局状态
这一点非常关键。 因为从这一章开始,你不只是学会了函数怎么写, 而是开始学会函数里的变量该怎么安放、怎么管理。
下一章我们继续讲 return 的本质:函数执行完到底返回了什么。 到那一章,你会发现,前面虽然已经用过很多次 return 了,但很多人其实并没有真正理解:函数结束时,到底是把什么交了回来。