Python 函数进阶:作用域、递归与高阶函数全攻略
文章目录
目录
前言
上回我们把函数的基础讲完了——怎么定义、怎么传参、怎么返回值,还有那个一次性小工具 lambda。函数这块的“地基”你已经打好了。
今天我们往上盖两层楼:
第一层:变量作用域 —— 为什么你在函数里改了一个变量,外面纹丝不动?为什么有时候你死活改不了外面的变量?这就是“作用域”在作怪。弄懂它,你才知道哪些变量在哪儿能用、在哪儿不能动。
第二层:递归函数 —— 函数自己调自己,听起来像俄罗斯套娃?没错,它是解决“重复做同一件事、但每次规模变小”这类问题的终极武器。
第三层:高阶函数 —— 函数不只是被调用的,它还可以被传来传去、当作参数塞给另一个函数。排序、过滤、映射……这些都是高阶函数的地盘。
这三块学完,你手里的函数就像从“小兵”进化成了“法师”——走位灵活、控制力强、还能召唤分身。
开整!🔥
第一章:变量作用域 —— 哪里的地盘哪里说了算
1.1 什么叫“作用域”?
简单说就是:变量在哪个范围内有效。
你在函数里面定义的变量,外面访问不到;你在外面定义的变量,函数里面能看见,但不一定能改。这就是作用域在管。
看个例子:
python
name = "全局老王" # 全局变量
def test():
name = "局部小李" # 局部变量
print("函数内部:", name)
test() # 函数内部:局部小李
print("函数外部:", name) # 函数外部:全局老王
📌 函数里面的
name和外面的name是两个不同的变量,各管各的,互不干扰。就像两个同名同姓的人,一个在A公司上班,一个在B公司上班,互相不串门。
1.2 局部变量 —— 只在函数内部有效
在函数内部定义的变量,叫局部变量,只能在这个函数里面用,函数一结束它就“消失”了。
python
def greet():
msg = "你好呀" # 局部变量
print(msg)
greet() # 你好呀
# print(msg) # ❌ 报错!NameError: name 'msg' is not defined
💡 局部变量就像一个工位上的笔记本——你在工位上随便写随便用,但你离开工位,这个本子别人就拿不到了。
1.3 全局变量 —— 在整个文件都有效
在函数外面(最顶层)定义的变量,叫全局变量,整个文件的所有函数都能看到它。
python
score = 100 # 全局变量
def show_score():
print("当前分数:", score) # 函数里能读
show_score() # 当前分数:100
1.4 在函数里想改全局变量?用 global
函数里可以读取全局变量,但想修改它,必须先声明 global:
python
count = 0 # 全局变量
def add_one():
global count # 👈 声明:我要改全局的 count
count += 1
add_one()
add_one()
print(count) # 2
如果不加 global 会怎样?
python
count = 0
def add_one():
count += 1 # ❌ UnboundLocalError
⚠️ 为什么会报错?因为 Python 看到你在函数里对
count做了赋值操作,就默认把它当成局部变量,但你又没给它初始值,所以报错。global就是在告诉 Python:“别瞎猜了,这个变量是全局的!”
1.5 函数嵌套 + nonlocal —— 跨层修改
函数里面还能再定义函数,这叫嵌套函数:
python
def outer():
x = 10 # 外层函数的局部变量
def inner():
nonlocal x # 👈 我要改外层函数的 x
x += 5
print("inner里面:", x)
inner()
print("outer里面:", x)
outer()
输出:
text
inner里面:15 outer里面:15
📌
nonlocal就像“越级上报”——不直接动全局,而是动上一层函数里的变量。适合闭包场景,暂时了解一下就行。
1.6 作用域规则总结
| 作用域 | 在哪里 | 谁能用 | 怎么声明 |
|---|---|---|---|
| 局部 | 函数内部 | 只能这个函数用 | 直接 x = 10 |
| 全局 | 整个文件 | 所有函数都能读 | 在函数外定义 |
| 全局修改 | — | 在函数里改全局 | 加 global x |
| 外层变量 | 嵌套函数 | 内层函数能用 | 内层加 nonlocal x |
第二章:递归函数 —— 自己喊自己
2.1 什么是递归?
递归函数就是在函数内部调用自己的函数。
听起来很绕?举个例子:你站在一面镜子前,镜子里反射出你,那个你又反射出另一个你……无限套娃。
递归的核心逻辑是:把一个大问题拆成一个小问题,小问题再拆成更小的问题,直到小到可以直接解决为止。
python
def countdown(n):
if n <= 0:
print("🎉 发射!")
return
print(n)
countdown(n - 1) # 自己调自己
countdown(5)
输出:
text
5 4 3 2 1 🎉 发射!
2.2 递归的两个关键要素
要素一:递归条件 —— 什么时候继续调用自己。
要素二:终止条件 —— 什么时候停下来(不然会无限递归,程序崩溃)。
python
def countdown(n):
if n <= 0: # 👈 终止条件:到底了,停下
print("🎉 发射!")
return
print(n) # 执行当前任务
countdown(n - 1) # 👈 递归条件:继续喊自己,但 n 变小了
2.3 经典递归:阶乘
阶乘的数学定义:n! = n × (n-1) × (n-2) × ... × 1
python
def factorial(n):
if n <= 1: # 终止条件:1! = 1
return 1
return n * factorial(n - 1) # 递归:n! = n × (n-1)!
print(factorial(5)) # 120
执行过程:
text
factorial(5) = 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
2.4 经典递归:斐波那契数列
斐波那契:1, 1, 2, 3, 5, 8, 13, 21, 34...(每一项是前两项之和)
python
def fib(n):
if n <= 2: # 终止条件:前两项都是 1
return 1
return fib(n - 1) + fib(n - 2) # 当前项 = 前两项之和
print(fib(6)) # 8
⚠️ 斐波那契递归虽然好理解,但效率极低(重复计算太多),实际工作中更多用循环。递归适合那种“问题天然能拆成子问题”的场景,比如遍历文件夹树。
2.5 递归注意事项
| 要点 | 说明 |
|---|---|
| 必须有终止条件 | 没终止条件会无限递归,程序直接炸 |
| 递归深度有限 | Python 默认递归深度约 1000 层,太深会报 RecursionError |
| 能迭代就别递归 | 递归优雅但费内存,小规模用递归,大规模用循环 |
第三章:高阶函数 —— 函数也可以“被传来传去”
3.1 什么是高阶函数?
在 Python 里,函数和数字、字符串一样,也是对象。这就意味着:
函数可以赋值给变量
函数可以作为参数传给另一个函数
函数可以作为返回值返回
把函数当成参数传来传去,就是高阶函数。就像你点外卖的时候,把“不要香菜”这个指令传给了商家。
python
# 普通函数:接收一个数字
def square(x):
return x ** 2
# 高阶函数:接收另一个函数作为参数
def process_data(data, func):
result = []
for item in data:
result.append(func(item))
return result
nums = [1, 2, 3, 4, 5]
print(process_data(nums, square)) # [1, 4, 9, 16, 25]
3.2 sorted() —— 自定义排序规则
sorted() 可以接收 key 参数,指定用哪个字段来排序——这就是高阶函数的典型应用。
python
students = [
{"name": "王小明", "score": 85},
{"name": "小红", "score": 92},
{"name": "小刚", "score": 78}
]
# 按分数排序(升序)
sorted_by_score = sorted(students, key=lambda x: x["score"])
print(sorted_by_score)
# [{'name': '小刚', 'score': 78}, {'name': '王小明', 'score': 85}, {'name': '小红', 'score': 92}]
3.3 filter() —— 筛选数据
filter() 接收一个判断函数和一个可迭代对象,返回满足条件的元素。
python
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 筛选偶数
even_nums = list(filter(lambda x: x % 2 == 0, nums))
print(even_nums) # [2, 4, 6, 8]
# 筛选分数 >= 80 的学生
students = [
{"name": "王小明", "score": 85},
{"name": "小红", "score": 92},
{"name": "小刚", "score": 78}
]
top = list(filter(lambda s: s["score"] >= 80, students))
print(top) # [{'name': '王小明', 'score': 85}, {'name': '小红', 'score': 92}]
3.4 map() —— 映射/批量转换
map() 接收一个转换函数和一个可迭代对象,把每个元素都应用一遍函数,批量加工。
python
nums = [1, 2, 3, 4, 5] # 每个数字平方 squared = list(map(lambda x: x ** 2, nums)) print(squared) # [1, 4, 9, 16, 25] # 用户名字全部转大写 names = ["alice", "bob", "charlie"] upper_names = list(map(lambda x: x.upper(), names)) print(upper_names) # ['ALICE', 'BOB', 'CHARLIE']
3.5 reduce() —— 累积计算(需要导入)
reduce() 在 functools 模块里,它会把结果不断累积,最后得出一个最终值。
python
from functools import reduce nums = [1, 2, 3, 4, 5] # 累积求和 total = reduce(lambda a, b: a + b, nums) print(total) # 15 # 累积求乘积 product = reduce(lambda a, b: a * b, nums) print(product) # 120
3.6 高阶函数全家福
| 函数 | 作用 | 返回值 |
|---|---|---|
sorted(数据, key=规则) | 按规则排序 | 新列表 |
filter(规则, 数据) | 筛选符合条件的 | 迭代器(可用 list() 转) |
map(规则, 数据) | 批量转换每个元素 | 迭代器(可用 list() 转) |
reduce(规则, 数据) | 累积计算,得出一个值 | 单个值 |
🎯 本章重点速查
| 概念 | 核心要点 |
|---|---|
| 局部变量 | 函数内部定义,只在函数内有效 |
| 全局变量 | 函数外定义,整个文件可读 |
global | 在函数里修改全局变量时声明 |
nonlocal | 嵌套函数中修改外层局部变量 |
| 递归 | 函数自己调自己,必须有终止条件 |
| 高阶函数 | 把函数当参数传来传去 |
sorted() | 自定义排序规则 |
filter() | 筛选符合条件的元素 |
map() | 批量转换每个元素 |
reduce() | 累积计算出一个最终值 |
🧁 结尾
到今天为止,你手里的函数已经从“刚出厂的小兵”进化成了“满级法师”——
作用域让你知道变量在哪儿能用、在哪儿不能动,像法师的技能范围,放对了有效果,放错了就炸自己。
递归让你的代码具备“分身术”,遇到能拆解的问题直接自我复制,层层击破,像法师的分身技能。
高阶函数让你把函数当棋子一样传来传去、组合使用,排序、筛选、映射一条龙,像法师的连招技能。
这三种能力合在一起,你手里已经攒了一套完整的代码组织体系。以后遇到稍微复杂一点的需求,你脑子里会先浮现出“这个逻辑能不能拆成一个函数?”“这个操作用 map 是不是更优雅?”“用递归还是用循环?”
高手和普通选手的区别,从来不是谁会的语法多,而是谁在写之前脑子里已经有了“最佳方案”。
下期我们开始搞 面向对象编程(OOP) ,从类到对象,从属性到方法,给代码盖一栋真正的大楼。咱们下期见~ 👋🐍
更多推荐


所有评论(0)