news 2026/10/18 9:45:22

Python函数练习全攻略:从参数传达到递归的进阶之路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python函数练习全攻略:从参数传达到递归的进阶之路

我刚开始学Python那会儿,函数练习是我又爱又恨的一块内容。爱的是自己写完一个函数、一行调用出结果,那种成就感真挺上头的;恨的是报错永远比预想来得快,参数漏传、作用域搞混、返回值没有按预期返回,光是看控制台那一串英文提示就能耗掉一晚上。后来靠着大量动手练习和反复拆解别人写的函数,才慢慢把这块硬骨头啃下来。回头看,Python函数练习就是入门路上的一道分水岭,它不只是语法问题,更是思维方式的转折点。这篇文章我想把自己练函数时用过的题目、踩过的坑、总结出来的方法论整理出来,适合刚学完循环和条件判断、正准备啃函数这块内容的朋友,也适合那些学了函数但只会照着抄、一让自己写就发懵的自学者。

1. 函数练习为什么是入门路上的分水岭

1.1 函数在项目中的真实分量

先别把函数当成语法书里的一个章节。任何一个能跑起来的Python项目,往底层拆,最后都会拆成若干个函数。发请求的是一个函数,清洗数据的是一个函数,解析结果的是一个函数,保存文件又是一个函数,主程序不过是在合适的时间把这些函数按顺序叫出来执行。函数就像是机器里的一个个齿轮,单个齿轮设计得好,整台机器才转得动。

我见过不少朋友,列表、字典、字符串操作都玩得挺溜,一到自己写函数就发怵。症状很统一:动手写一个复杂点的逻辑时,所有代码都堆在一个大块里,用几十行print去观察每一步的值,改一个局部功能会带出一串连锁问题。这种情况的根源,就是函数练习没跟上。函数最直接的价值是隔离复杂度,把一个大问题切成一个个小块,每一块用一个函数表达。你不练这个,后面学类、学装饰器、学各种设计模式,全都是空中楼阁。

1.2 练习函数到底在练什么

练习函数看上去是在练语法,实际上是在练三样东西。

首先是语法熟练度。def怎么带参数、参数默认值怎么设置、怎么收集任意多个参数、return在什么位置生效,这些不练到肌肉记忆,写代码的时候脑子就一直卡在语法细节上,根本没有余力去思考逻辑设计。其次是设计思路。拿到一个需求,能不能把它拆成几个小函数,每个函数只做一件事,并且这件事足够简单、足够纯粹,这是区分新手和熟练工的关键。最后是调试能力。函数写出来后运行结果不对,是参数传错、逻辑写错、还是返回值没接住,这个排查过程本身就是在练内功。

记住一个原则:看得懂和写得出来是两码事,写得出来和写得对又是另一码事。很多人看教程时觉得函数很简单,真到自己动手,才发现理解是有错觉的。

2. 动笔之前先理清参数、返回和作用域

2.1 参数体系:位置参数、默认参数、可变参数

参数这块是函数练习里最容易“半懂不懂”的知识点。很多初学者一开始只知道def add(a, b): return a + b,等遇到复杂一点的调用场景就懵了。Python的参数体系其实很清晰,我建议把它当成一张表来记。

def print_info(name, age=18, *hobbies, **others): print(f"姓名:{name},年龄:{age}") if hobbies: print("爱好:", hobbies) if others: print("其他:", others) # 位置参数 print_info("A同学") # 位置参数 + 默认参数覆盖 print_info("B同学", 20) # 加上任意多个爱好 print_info("C同学", 22, "阅读", "跑步") # 再加上关键字参数 print_info("D同学", 23, "编程", 城市="某地")

这里name是必填的位置参数,age带默认值可以不传;*hobbies会把调用时多传的位置参数收进一个元组;**others会把多余的关键字参数收进一个字典。这个设计让函数既能处理固定信息,又能处理不确定数量的扩展信息,在真实项目中非常常用。

要特别注意参数的排列顺序:默认参数要放在位置参数后面,*args要放在默认参数后面,**kwargs要放在最后面。顺序写错了,解释器直接报语法错误,这是新手特别容易踩的坑。

2.2 return与None的分界线

函数练习里还有一个很经典的错误,就是搞不清返回值到底是什么。我见过不少朋友写代码,函数里用print把结果打出来,看着控制台有输出,于是放心拿去参与计算,结果拿到一个None,一脸茫然。

def square(x): return x * x def nothing(): # 没有 return,函数默认返回 None pass result = nothing() print(result) # None

Python有个规则需要刻进脑子里:如果一个函数没有return语句,或者return后面没有接任何值,那么函数调用的结果就是None。None不是0也不是空字符串,它是独立的特殊值。有些代码里你看到调用没有报错,但结果始终不对,大概率就是拿到了None,然后又拿None去做了加减法或字符串拼接。

另外要注意,return的作用是结束函数执行,return后面的代码永远不会跑。多返回值实际上是返回一个元组,调用时用a, b = foo()这种写法来拆开,这在练习里很常见。

2.3 作用域和那个高频报错

作用域是函数练习里抽象感比较强的一块,但它又直接影响你能不能写出正确代码。简单来说,函数内部定义的变量,默认是局部变量,出了函数就找不到了;函数内部想修改全局变量,需要加global声明;嵌套函数里想修改外层函数的局部变量,需要加nonlocal声明。

count = 0 def increase(): global count count += 1 increase() print(count) # 1

如果代码里去掉global,运行就会报UnboundLocalError: local variable 'count' referenced before assignment。这句报错在练习中出现频率极高,看到它基本就能锁定是作用域问题:你在函数内给某个变量赋值了,但在赋值之前又读取了它,Python认为这个变量是局部的,可它还没被定义。解决办法很简单,要么加global声明,要么把变量放进列表或字典里传进去,要么干脆换个设计思路。

3. 一套递进式函数练习方案,直接照着做

3.1 从热身题到综合题的分层清单

练习函数最怕没有章法,想到什么写什么。我自己练的时候总结了一套分层递进的题目清单,从最简单的热身题开始,逐步增加难度,你可以直接照着做。

第一层是数学小函数,目标是练基本语法。比如写一个函数max_of_two(a, b),返回两个数中较大的;写一个is_even(n),判断奇偶;写一个area_of_circle(r),计算圆面积。这些题目一眼就能看懂,适合用来建立手感。

第二层是字符串处理,目标是练循环和条件的组合。比如count_vowels(s)统计字符串里元音字母的个数,reverse_str(s)返回反转后的字符串。这类题目会逼你处理字符串的每一个字符,是很好的综合练习。

第三层是容器处理,目标是练列表、集合的综合运用。比如unique_list(lst)去掉重复元素且保持原有顺序,flatten(nested_lst)把二维列表打平成一维列表。这里有个很实用的小技巧:集合可以去重但不保序,所以用set记已经出现过的元素,用list维护最终结果顺序。

def unique_list(lst): seen = set() result = [] for item in lst: if item not in seen: seen.add(item) result.append(item) return result print(unique_list([3, 1, 3, 2, 1, 4])) # [3, 1, 2, 4]

第四层是综合分析题。比如analyze_text(text),输入一段文本,返回大写字母数、小写字母数、数字数、其他字符数的字典。这种题目把字符串遍历、条件判断、字典操作都串起来了,做完会很有成就感。

3.2 用递归题逼自己跳出循环思维

递归是函数练习里绕不过去的一道坎。很多人第一次接触递归时会觉得它绕,其实递归并不复杂,它的核心规则只有三条:有明确的基线条件让递归停止;有递归表达式把大问题化简成小问题;每次递归都在向基线条件靠近。

def factorial(n): if n <= 1: return 1 return n * factorial(n - 1)

这是最经典的阶乘递归。n等于1时返回1,这是基线;否则用n乘以n-1的阶乘,这是递推。逻辑很简单,但它把“函数自己调用自己”这个核心概念讲清楚了。

进阶一点可以试试斐波那契数列。直接写递归版非常简单,但n稍微大一点就会发现运行越来越慢。原因很好理解:同样的子问题被重复计算了无数次。解决方法是加一个缓存。

from functools import lru_cache @lru_cache(maxsize=None) def fib(n): if n <= 1: return n return fib(n - 1) + fib(n - 2) print(fib(50))

加了缓存之后,fib(50)计算速度飞快。这个练习能让人直观地理解算法优化中“空间换时间”的思路。另外要记住,Python默认的递归深度上限大约是一千层,递归层数太深会报RecursionError,所以递归练习要选对题目,适合分治类的问题,不适合深度超限的场景。

3.3 用迷你项目练函数拆解

练到后期,只做单独的函数题已经不够过瘾了,可以尝试给自己安排一个迷你项目。我以前练过一个迷你记账本,虽然只有几十行代码,但函数拆解的体验非常到位。

def add_record(records, kind, amount, note): records.append({"kind": kind, "amount": amount, "note": note}) def total_income(records): return sum(r["amount"] for r in records if r["kind"] == "income") def total_expense(records): return sum(r["amount"] for r in records if r["kind"] == "expense") def balance(records): return total_income(records) - total_expense(records) def show_records(records): for r in records: print(f"{r['kind']}:{r['amount']}({r['note']})")

主程序里的循环只负责接收输入,然后调用对应函数。每个函数都很短小,功能单一。这样做的好处是,想改打印格式只动show_records,想改统计逻辑只动total_income和total_expense,互不影响。这个练习的真正目的不是记账,而是体会“一个函数只做一件事”这个设计原则在真实场景中的价值。等你练到能很自然地把项目拆成函数,你的组织能力就已经上一个台阶了。

4. 练习中高频出现的错误与排查实录

4.1 参数传递导致的TypeError

函数练习中碰到最多的报错类型就是TypeError,而且报错信息往往已经把答案写在脸上了。

  • TypeError: f() missing 1 required positional argument: 'b',意思是调用时少传了必填参数。
  • TypeError: f() got an unexpected keyword argument 'b',意思是函数里根本没有这个参数名,很可能是关键字参数拼错了。

遇到这类报错别慌,先把函数定义的参数列表和调用处的参数列表对照一遍,大多数情况下立刻就能定位问题。一个小习惯很有效:写调用代码之前,先在注释里写出函数的签名,也就是函数名、参数、返回值,然后再填调用语句,能少走很多弯路。

4.2 作用域导致的UnboundLocalError

前面提到过的UnboundLocalError是作用域问题的典型症状。它出现时,代码里多半存在“函数内部某个变量既被读取又被赋值”的情况。我一开始看到这个报错也一头雾水,后来才明白,Python在编译函数的时候会扫描整个函数体的赋值语句,只要某个变量名在函数内部被赋值过,它就认定这个变量是局部的,所以函数体前面即使引用了同名全局变量,也会被视为引用未定义的局部变量。

解决思路有两种。如果确实需要修改全局变量,就用global声明;但更好的思路是想办法避免这种依赖,把全局变量的值作为参数传进函数,再把新值用return带出来。后一种方式更清晰,也更利于测试。

4.3 递归与默认参数两类隐蔽坑

递归类错误最常见的就是RecursionError: maximum recursion depth exceeded。这通常意味着基线条件没写对,或者递归过程没有收敛。排查时可以加一条print打印当前参数,观察递归过程是不是在逼近终点。如果无限循环,很快就能看出来。

默认参数的坑更隐蔽。很多初学者写过类似下面这种代码:

def add_item(item, items=[]): items.append(item) return items print(add_item("a")) # ['a'] print(add_item("b")) # ['a', 'b'],这里就出问题了

第二次调用时,列表里莫名其妙多出了第一次调用留下的元素。原因是默认参数在函数定义时就被创建了,之后每次调用只要没有显式传items,用的都是同一个列表。正确写法是把默认值设为None,在函数内部创建新列表。

def add_item(item, items=None): if items is None: items = [] items.append(item) return items

这类问题在真实项目中出现时特别磨人,因为代码逻辑本身没有错,纯粹是默认参数共享了同一个可变对象。遇到这个坑,赶紧自查一下有没有把列表、字典这类可变对象放在默认参数里。

我把这些问题整理成一个速查表,方便你遇到报错时直接对照。

报错信息常见原因排查与解决办法
missing positional argument调用时少传了必填参数对照函数签名补齐参数
unexpected keyword argument写了不存在的参数名检查关键字参数拼写
UnboundLocalError函数内变量既被读取又被赋值加global,或改成参数传递加return
RecursionError递归没有基线或层数太深检查基线条件,改用循环或缓存
结果全部为None函数缺return,或分支没有覆盖全检查每个分支是否都有return

5. 函数练习之后,我的几点进阶体会

5.1 先写函数签名再写实现

我练到后面发现一个效率提升很大的习惯:动手编码之前,先在纸上写下函数签名,也就是函数名、参数列表、返回值的含义,然后再写实现。这个过程和先画图纸再施工很像,看起来多花了一分钟,实际上能避免大量返工。

比如说,需求是“统计一段文本中每个单词出现的次数”,我会先写下来:函数名count_words,参数text,返回值dict,然后才去写代码。签名一旦清晰了,实现起来思路就顺了。很多新手容易犯的错是拿到需求就直接开写,写到一半发现函数要复用但接口设计不对,又推倒重来。先设计签名,就是最好的预防。

5.2 给函数写docstring和简单测试

写函数的第一行,我用三引号写文档字符串,说明这个函数干什么、参数是什么、返回什么。这个习惯一开始觉得啰嗦,但过一个月再回来看自己的代码,才知道当初的注释有多重要。更进阶一点的做法是给函数写测试用例,用assert语句验证输入输出是否符合预期。

def max_of_two(a, b): """返回两个数中较大的那个""" return a if a > b else b assert max_of_two(1, 2) == 2 assert max_of_two(5, 5) == 5 assert max_of_two(-1, -10) == -1

这个练习看起来简单,作用却很大。写测试的过程会逼你重新思考函数的所有边界情况,比如空字符串、负数、重复元素、超大数字。很多函数练习时觉得写对了,一推边界情况就出问题,测试能帮你把这些漏洞挖出来。

5.3 每周挑一个生活场景做函数化改造

函数练习最重要的还是保持手感。我自己有段时间规定自己每周找一个生活场景,用函数去组织代码。比如计算购物小票总价、统计一段文字里的词频、把一堆文件名按照规则重命名。这些场景都不需要很大的代码量,但能持续锻炼“拆解问题”的思维。

做这种练习时,如果发现自己写出的函数超过二十行,就会停下来看看能不能拆得更细。拆不出来的时候,说明对问题的理解还不够充分。这种练习没有标准答案,但每做一次,对函数的理解都会更扎实一些。

说到最后,我想起自己练函数时最后悔的一件事:早期觉得函数练习题太简单,跳过了大量的基础训练,直接去看高级用法,结果基础不牢,后面越学越虚。后来老老实实把基础题刷了一遍,才感觉到什么叫“手顺”。这个内容后续还可以扩展的方向很多,但最核心的还是那句朴素的话:多写、多拆、多测。函数能不能练好,从来不是智商问题,是用功方法的问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 0:33:39

开源AI智能体实战:从选型到部署,打造可上岗的数字员工

2026年再聊“开源AI智能体”&#xff0c;我不太想把它当概念讲了。过去两年我亲手搭过、也拆过不少开源智能体项目&#xff0c;从早期只会调API的聊天机器人&#xff0c;到如今能跑知识问答、处理工单、跨系统填数据、自动生成项目周报的数字员工&#xff0c;中间踩的坑足够写一…

作者头像 李华
网站建设 2026/10/16 23:00:42

遥感卫星图像YOLOV5三类别检测数据集构建与训练实战

简介&#xff1a;这份资源面向从事遥感图像目标检测的算法工程师、研究生及竞赛选手&#xff0c;提供一套按YOLOv5目录规范整理的卫星图像数据集&#xff0c;可直接投入训练&#xff0c;省去格式转换与清洗环节。数据覆盖飞机、油罐、船只三个类别&#xff0c;图像为800600的RG…

作者头像 李华
网站建设 2026/10/16 14:44:32

Java面试核心:volatile、HashMap、线程池与JVM实战解析

1. 一场让我差点绷不住的面试开场先交代一下背景。我在某互联网公司做Java方向的技术面试官&#xff0c;做了差不多五年&#xff0c;面过的人数没有一千也有八百。按公司流程&#xff0c;面试分两轮&#xff0c;第一轮通常是基础技术考察&#xff0c;我常年负责这一轮。我给自己…

作者头像 李华
网站建设 2026/10/17 18:05:47

Java WebSocket实时聊天室实战:从轮询到长连接的架构跃迁

1. 项目概述&#xff1a;为什么一个“多人实时聊天室”值得你花三小时认真读完“构建多人实时聊天室&#xff1a;Java与WebSocket实战”——这标题里藏着的&#xff0c;不是又一个教你怎么写Hello World的Demo&#xff0c;而是一条通往真实业务场景的窄门。我带过不少刚毕业的开…

作者头像 李华
网站建设 2026/10/16 12:22:42

从aaaa.rar拆解Paint画图软件:Win32/GDI绘图核心与避坑指南

简介&#xff1a;这是一款模仿Windows画图程序的小型绘图软件&#xff0c;面向只需完成基础图像编辑与简单创作的用户&#xff0c;尤其适合教学演示或日常快速绘图场景。压缩包共10个文件&#xff0c;约13KB&#xff0c;以C Builder工程文件为主&#xff0c;包含cpp与h源码、df…

作者头像 李华
网站建设 2026/10/13 17:11:22

zsh终端效率革命:从命令行新手到高产开发者的跃迁路径

1. 为什么现在还要学zsh&#xff1f;一个终端壳的“隐性生产力革命” 你可能已经用Mac或Linux很多年&#xff0c;每天打开终端敲 ls 、 cd 、 git status &#xff0c;顺手按两下Tab补全&#xff0c;觉得一切理所当然。但某天你看到同事的终端里输入 git che 再按Tab&…

作者头像 李华