查看全课大纲(第 15 / 51 节)
- 1.为什么人人都该学点编程?
- 2.计算机与程序设计:你的电脑到底在“想”什么?
- 3.Python 这门语言,到底有什么魔力?
- 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
- 5.如何安装一个更强大的代码编辑器?
- 6.汇率兑换1.0:你的第一个“钱生钱”小程序
- 7.你的第一行代码,藏着哪些秘密?
- 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
- 9.汇率兑换程序,如何让它“聪明”地判断币种?
- 10.汇率兑换程序能一直运行,直到我喊停吗?
- 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
- 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
- 13.怎么用 Python 画一个会动的五角星?
- 14.如何让程序自动画出不同大小的五角星?
- 15.函数自己调用自己,会发生什么?
- 16.用递归画出神奇的分形树
- 17.Python 怎么计算你一天躺着不动能消耗多少热量?
- 18.如何让程序“活”起来,持续为你服务?
- 19.如何让 BMR 计算器更“聪明”?
- 20.当程序遇到“不按套路出牌”的用户怎么办?
- 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
- 22.如何用列表记录每一周的存款?
- 23.如何让程序自己数数?—— 用 for 循环解放你的双手
- 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
- 25.52周存钱挑战:怎么根据日期自动查存款?
- 26.Python 怎么判断一年中的第几天?
- 27.列表 vs 元组:哪个更适合存储月份天数?
- 28.用集合给月份分个队,换个思路算天数
- 29.把月份和天数“锁”在一起:用字典给数据配个对
- 30.Python 怎么判断密码强弱?
- 31.Python 怎么给密码设置“防沉迷”系统?
- 32.Python 怎么把密码保存到文件里?
- 33.Python 怎么读取文件里的密码?
- 34.Python 怎么把密码检查工具“打包”起来?
- 35.Python 怎么把文件操作也“打包”起来?
- 36.计算机怎么“扔骰子”?——用Python模拟随机事件
- 37.如何用 Python 模拟掷两个骰子?
- 38.怎么用 Python 把骰子轨迹画出来?
- 39.骰子点数分布长啥样?用直方图一眼看穿!
- 40.用科学计算库让骰子程序“起飞”
- 41.空气质量指数怎么算?手把手教你写一个AQI计算器
- 42.空气质量指数计算:从零开始,让程序看懂空气
- 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
- 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
- 45.Python 怎么让程序自己判断文件类型?
- 46.如何让程序自己上网查空气质量?
- 47.用美味的汤来解析网页:Beautiful Soup 入门
- 48.如何让爬虫自动抓取全国所有城市的空气质量?
- 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
- 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
- 51.空气质量数据怎么“洗”干净,还能画成图?
函数自己调用自己,会发生什么?
约 13 分钟
小象实战讲义 · Python 入门:8 个小项目边玩边学
还记得我们上节课画的“五角星家族”吗?我们用循环让程序一口气画了6个大小递增的五角星。代码虽然能跑,但主程序里又是循环又是更新参数,看起来有点“热闹”。这节课,我们要玩一个更酷的魔法:让函数自己调用自己,把那些“热闹”的操作都封装起来。学完这一节,你的主程序会变得超级简洁,而画图的“魔法”都藏在函数里,就像你只需要按下一个“开始”按钮,程序就会自己完成所有工作!
💡 核心导读
- 新魔法:递归函数:认识一种“自己调用自己”的神奇函数。
- 封装的艺术:把复杂的循环和参数更新逻辑,都塞进一个函数里,让主程序变得清爽。
- 关键一步:终止条件:没有它,递归函数会像失控的陀螺一样转个不停,我们必须学会如何安全地“刹车”。
- 成果预览:我们将得到一个主程序只有一行核心代码的“五角星家族”绘制程序。
回顾与思考:上节课的“热闹”主程序
我们先快速看一眼上节课(2.0版本)画五角星家族的核心代码片段:
size = 50
while size <= 100:
draw_pentagram(size) # 调用画一个五角星的函数
size = size + 10 # 更新大小参数这段代码在主程序里做了两件事:1. 循环调用函数;2. 更新size参数。想象一下,如果画图的逻辑更复杂(比如要画分形树!),主程序就会变得很长、很难维护。
这节课,我们只做一件事:把“循环调用”和“参数更新”这两件事,从主程序搬到函数内部去。这样,主程序只需要“启动”一次函数,剩下的就交给函数自己“递归”完成。
揭秘递归:函数里的“套娃”魔法
什么是递归?简单说,就是一个函数在执行过程中,调用了它自己。
这听起来有点奇怪,自己怎么调用自己?我们用一个生活化的比喻来理解:
想象你要打扫一个有很多房间的大房子。你的“打扫函数”是这样工作的:进入一个房间,先打扫这个房间,然后发现这个房间里面还有一个小房间,于是你再次调用「打扫函数」,去打扫那个小房间。小房间打扫完了,回到大房间,继续完成大房间的打扫。这个过程就是“递归”。
对应到我们的代码:
- “进入房间打扫” 对应 “画一个固定大小的五角星”。
- “发现小房间” 对应 “画完当前五角星后,把尺寸变大一点”。
- “再次调用打扫函数” 对应 “函数自己调用自己,但传入新的、更大的尺寸参数”。
这样,对于主程序来说,它只需要说一句:“去把50尺寸开始的五角星家族画出来”。至于怎么画下一个、画到什么时候停,都是函数内部的事情。这就是封装,也是递归的魅力。
动手实战:打造递归版五角星绘制函数
让我们开始改造代码。我们会创建一个新的函数 draw_recursive_pentagram。
第一步:搭建函数框架 我们先写出函数的基本样子,它接收一个size参数。
def draw_recursive_pentagram(size):
"""
递归绘制五角星
"""
# 我们先在这里写绘制一个五角星的逻辑
pass # pass是占位符,表示"这里先空着"第二步:填入绘制单个五角星的逻辑 这部分和之前完全一样,我们直接搬过来。
def draw_recursive_pentagram(size):
"""
递归绘制五角星
"""
# 绘制一个五角星
count = 1
while count <= 5:
turtle.forward(size)
turtle.right(144)
count += 1第三步:实现“递归”的核心——自己调用自己 画完当前这个五角星后,我们不是结束,而是要准备画下一个更大的。
- 更新参数:让
size增加10,为画下一个做准备。 - 判断是否继续:如果新的
size还没有超过100,我们就再次调用自己(draw_recursive_pentagram),并把新的size传进去。
def draw_recursive_pentagram(size):
"""
递归绘制五角星
"""
# 绘制一个五角星
count = 1
while count <= 5:
turtle.forward(size)
turtle.right(144)
count += 1
# 递归部分:准备画下一个
# 1. 更新参数
size += 10
# 2. 判断是否达到终止条件,如果没达到,就自己调用自己
if size <= 100:
draw_recursive_pentagram(size)看!if size <= 100: 这一行就是至关重要的“终止条件”。它保证了当size增加到110时,函数不会再调用自己,递归过程就此停止。如果没有这个条件,函数就会无限地自己调用自己,直到程序崩溃。
第四步:简化主程序 现在,我们惊艳的主程序来了!它只需要初始化画笔,然后调用一次我们的递归函数即可。
import turtle
def draw_recursive_pentagram(size):
"""
递归绘制五角星
"""
count = 1
while count <= 5:
turtle.forward(size)
turtle.right(144)
count += 1
size += 10
if size <= 100:
draw_recursive_pentagram(size)
# 主程序
turtle.penup()
turtle.backward(200)
turtle.pendown()
turtle.pensize(2)
turtle.pencolor('red')
# 魔法启动!只需要调用一次
draw_recursive_pentagram(50)
# 注意:实际运行时,需要点击图形窗口才能关闭。
# 为了你能直接运行看效果,这里省略了 turtle.exitonclick()。
# 完整可运行代码请参考本章的课程资源附件。运行一下,是不是和上节课的效果一模一样?但对比一下主程序,是不是干净利落多了?所有“如何画出一个家族”的细节,都被完美地隐藏在了 draw_recursive_pentagram 这个函数里。
递归是如何一步步运行的?(选读)
如果你对“自己调用自己”的过程感到好奇,可以跟着下面的步骤在脑子里“运行”一遍程序:
第一次调用:
draw_recursive_pentagram(50)。- 函数开始执行,用
size=50画了一个五角星。 - 然后
size变成60,判断60 <= 100为真。 - 于是,在第一个函数还没执行完的时候,它发起了第二次调用:
draw_recursive_pentagram(60)。
- 函数开始执行,用
第二次调用:
draw_recursive_pentagram(60)。- 这是一个全新的函数执行过程,它有自己的
size参数(值为60)。 - 它用60画一个五角星。
- 然后
size变成70,判断70 <= 100为真。 - 发起第三次调用:
draw_recursive_pentagram(70)。
- 这是一个全新的函数执行过程,它有自己的
依次类推… 直到
draw_recursive_pentagram(100)被调用。- 它用100画一个五角星。
size变成110,判断110 <= 100为假。- 这次,它没有再调用自己。函数执行完毕,返回。
逐层返回:当
draw_recursive_pentagram(100)执行完后,会返回到调用它的地方,也就是draw_recursive_pentagram(90)函数中if语句的下一行(其实后面没代码了)。draw_recursive_pentagram(90)也执行完毕,返回到draw_recursive_pentagram(80)……最终,所有的函数调用依次结束,返回到最初的主程序。
这个过程就像钻进一个 Russian Doll(俄罗斯套娃),从最大的娃钻到最小的娃,然后再一层层钻出来。
📝 动手练一练
- 参数实验:如果把主程序中调用的
draw_recursive_pentagram(50)改成draw_recursive_pentagram(80),最终会画出几个五角星?分别是多大的? - 终止条件改造:如果我们想画10个五角星就停止,而不是画到size大于100停止,应该怎么修改递归函数里的终止条件?(提示:可以引入一个额外的参数来计数)
- “失控”的递归:作为实验(请勿长时间运行),你可以尝试把递归函数中的终止条件
if size <= 100:暂时注释掉,然后只调用draw_recursive_pentagram(1)。猜猜会发生什么?这说明了什么?
👉 点击查看第1题参考答案
调用 draw_recursive_pentagram(80) 后,程序会依次绘制 size 为 80, 90, 100 的五角星。当 size 更新为 110 时,不满足 <=100 的条件,递归停止。所以总共会画出3个五角星,大小分别为80, 90, 100。
👉 点击查看第2题参考答案
思路是增加一个参数 count 来记录已经画了多少个五角星。每次调用自己时,count 加1,当 count 达到10时停止。
关键代码修改如下(注意,这是一个独立的函数片段,展示了如何修改):
def draw_recursive_pentagram_v2(size, current_count):
"""
递归绘制五角星,限制绘制数量
"""
# 绘制一个五角星
count = 1
while count <= 5:
turtle.forward(size)
turtle.right(144)
count += 1
# 更新参数:大小增加,计数增加
size += 10
current_count += 1
# 终止条件:画满10个就停
if current_count < 10:
draw_recursive_pentagram_v2(size, current_count)
# 调用时传入初始计数0
# draw_recursive_pentagram_v2(50, 0)👉 点击查看第3题参考答案
如果注释掉终止条件,函数在画完一个五角星后,会无限制地调用自己(size 会不断增加,但判断条件永远为真)。这会导致程序不断消耗内存创建新的函数调用,最终通常会因超出最大递归深度而崩溃(Python会报 RecursionError)。
这说明:在设计递归函数时,定义一个清晰且一定能被触发的终止条件是绝对必要的,否则就是“死递归”。
本章小结
恭喜你!到本节为止,我们已经为绘制“分形树”这个终极目标,攒齐了所有关键的工具包:
- 1.0:学会了用
turtle库画基本图形(五角星)。 - 2.0:学会了用循环来重复操作,画出系列图形。
- 3.0:学会了用递归来优雅地封装复杂的重复逻辑。
递归是一个强大的思想,它能让代码变得非常简洁,尤其适合处理“自相似”的结构——比如我们下节课要画的树,树干分叉成树枝,树枝又分叉成更小的树枝,这正是递归大显身手的地方!
📋 行动清单
- 运行本节最终的递归版代码,感受主程序“一键启动”的简洁。
- 尝试修改终止条件(如练习2),体验控制递归过程的感觉。
- 在脑海里模拟一遍
draw_recursive_pentagram(50)的完整执行流程,理解“套娃”式的调用与返回。
准备好迎接最酷的分形树了吗?我们下节课,树上见!
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第3章代码包:分形树的绘制(含全部版本 4 个 .py)ZIP · 2KB