
Python 的函数这块一直是很多新手从“写脚本”迈向“写程序”的关口。我自己带过不少人学 Python发现一个规律列表、字典、循环这些内容大家学得都挺快但一进入函数就开始出现各种“看起来没问题、一跑就报错”的情况。所以当时整理这份“Python 测验3函数”目的不是为难谁而是把函数这个主题里最容易踩的坑、最容易混淆的概念全部用题目的形式过一遍。这篇文章就把这套测验的完整内容、出题逻辑、答案解析顺带把我在批改过程中反复见到的错误和排查思路都放出来给正在学函数的同学一份能够“自己对着查”的参考资料。先说这份测验适合谁。如果你刚刚学完 Python 的基础语法会写 for 循环和 if 判断但对def、参数传递、返回值、作用域这些概念还处于“好像懂但不太自信”的状态这份题就非常合适。当然如果你是已经写了一段时间 Python 的开发者也可以快速扫一遍用来查漏补缺——尤其是默认参数、可变对象、闭包这几类问题上很多老手也会偶尔翻车。1. 这份“函数测验3”到底在测什么1.1 为什么把函数单独拿出来做一套测验函数是 Python 里第一个真正的“抽象单元”。在这之前你写的代码是自上而下顺序执行的变量就是变量循环就是循环。但一旦引入函数你就得同时处理几个新概念参数怎么传、返回值怎么接、变量作用域怎么划分、函数对象本身能不能像普通值一样被传来传去。这几件事单独拿出来都不难但它们凑在一起就很容易让人懵。举个例子很多初学者能理解“定义一个函数”也能理解“调用一个函数”但问他“为什么这里要写return不写行不行”他就开始含糊了。再比如函数里面能不能修改全局变量、默认参数为什么不能写成空列表、lambda怎么捕获循环变量——这些知识点几乎只有在测验或者实际调试中才会暴露出来。所以这份测验的设计原则很明确不考死记硬背的语法条目专考“你有没有真的理解这个机制”。1.2 考点分布与出题逻辑整套测验围绕函数这个主题覆盖了以下几个方面函数定义与调用、参数传递方式位置参数、关键字参数、默认参数、可变参数、返回值处理、作用域规则、lambda 表达式、以及几个高频内置函数的使用。我简单整理了一下考点分布考点对应题号难度函数定义与基本调用第1、2题基础默认参数与可变对象第3题核心易错参数传递方式第4、5题中等返回值与 None第6题基础作用域规则第7、8题核心内置函数abs/map/filter/sorted第9题中等lambda 与回调函数第10题进阶这里特别说一下第3题、第7题和第10题是我故意埋的“坑题”。这三道题考察的并不是生僻语法而是日常开发中特别常见的三种场景函数默认值用可变对象、函数内部修改全局变量、循环里创建 lambda。如果这三道题你能不看答案就说出正确的输出结果那说明函数这一块的基本功是扎实的。1.3 难度梯度参考整套题并不是从头到尾一个难度我按梯度分了三个层次。第一层是“热身题”集中在函数定义、参数传递的基础用法上哪怕你只是刚看完函数章节的教学视频也应该能拿满分。第二层是“进阶题”涉及默认参数、作用域、内置函数这些内容是笔试面试中经常出现的类型。第三层是“挑战题”比如 lambda 在循环中的捕获问题这类问题即使是有经验的开发者也需要仔细想一下才能答对。如果让我给一个参考标准热身题错了 2 道以上说明基础调用还没有完全掌握建议回炉看函数定义那一章进阶题错了 2 道以上说明你在实际写代码时可能经常遇到“莫名报错”的情况本文第 3 章的内容应该仔细看挑战题错了很正常能答对说明你已经具备了阅读源码的基本素养。2. 完整测验题与逐题答案解析2.1 第一组函数定义与基本调用这一组是热身题考察的是最基础的函数调用逻辑。第1题阅读下面的代码写出输出结果def greet(name): return Hello, name print(greet(Alice))答案Hello, Alice解析这一题没有太多可说的函数接收一个参数name返回拼接后的字符串。唯一值得提醒的是print(greet(Alice))这行代码它先执行了greet(Alice)拿到返回值再把返回值交给print输出。很多初学者会混淆“函数内部打印”和“函数返回值”的区别这道题就是用来区分这两件事的。第2题以下代码能正常运行吗如果能输出什么def add(a, b): result a b print(add(1, 2))答案能运行输出None解析这就是经典的“函数没有 return”问题。add函数内部计算了a b但没有返回结果所以函数执行完后默认返回None。很多初学者会把result a b误以为是“返回结果”实际上它只是给局部变量赋值。我在实际教学里发现这道题值得反复强调。函数内部所有没有return的计算结果都只是“存在函数内部”外面拿不到。如果你发现自己调用函数后得到的值一直是None可以先检查一下是不是忘了写return。2.2 第二组参数传递与返回值第3题下面的函数定义了一个默认参数连续调用两次后结果是什么def append_item(item, lst[]): lst.append(item) return lst print(append_item(1)) print(append_item(2))答案[1] [1, 2]解析这是 Python 里非常经典的“可变默认参数”陷阱。lst[]这个空列表在函数定义的时候只会创建一次之后每次调用如果没有传入lst用的都是同一个列表对象。所以第二次调用的时候列表里已经有一个1了再追加2结果就是[1, 2]。正确的写法是把默认参数设为None在函数内部再创建新列表def append_item(item, lstNone): if lst is None: lst [] lst.append(item) return lst这道题在面试里出现的频率相当高。很多写了几年 Python 的人如果平时没有刻意留意也会在这里栽跟头。第4题请写出代码的输出结果def func(a, b, c): return a b c print(func(1, 2, 3)) print(func(a1, b2, c3)) print(func(1, b2, c3))答案6 6 6解析这题考察的是位置参数和关键字参数的混用。Python 允许在同一个调用中混合使用两种传参方式但顺序上有限制位置参数必须出现在关键字参数之前。func(1, b2, c3)中1传给ab和c通过关键字指定完全合法。需要特别注意的是func(a1, 2, c3)这种写法是错的因为关键字参数后面又出现了位置参数Python 解释器会直接报SyntaxError。第5题请写出代码的输出结果def func(*args): return sum(args) print(func(1, 2, 3, 4))答案10解析*args会把所有多余的位置参数打包成一个元组。在函数内部args就是一个普通的元组可以直接迭代、求和、切片。这道题如果答错了多半是忘了*args打包后是一个元组而非列表这件事。2.3 第三组作用域与闭包第6题阅读下面的代码写出输出结果x 10 def func(): x 20 return x print(func()) print(x)答案20 10解析这是作用域的基础题。函数内部的x 20创建了一个新的局部变量它和全局变量x是两个完全不同的东西。所以函数返回的是20而全局变量x依然是10。真正的坑在下一题那就是“如果函数内部想修改全局变量会发生什么”。第7题阅读下面的代码写出输出结果x 10 def func(): x x 1 return x print(func())答案UnboundLocalError: local variable x referenced before assignment解析这是让我最意外的一道题因为它考的是报错而不是某个具体的输出值。x x 1这行代码里面Python 在函数内部只要出现了对某个变量的赋值操作就会把这个变量视为局部变量。所以x x 1的右侧在访问x的时候访问的是“还没赋值过的局部变量”于是直接报错。如果确实想在函数内部修改全局变量需要用global关键字声明x 10 def func(): global x x x 1 return x print(func())输出结果是11。但我的建议是非必要不要用global。全局变量被函数修改之后整个程序的执行路径会变得非常难追踪。日常开发中更好的做法是把需要操作的变量作为参数传入把结果通过返回值传出来。第8题以下代码使用了嵌套函数写出输出结果def outer(): x 1 def inner(): nonlocal x x 1 return x return inner() result outer() print(result)答案2解析nonlocal关键字用于在内层函数中修改外层函数的局部变量。x 1就是把外层x从1改成2所以输出是2。如果把nonlocal x那行去掉就会遇到和第7题一样的UnboundLocalError。这道题考察的已经进入闭包和嵌套函数层面了实际开发中你写装饰器的时候几乎天天和这类结构打交道。2.4 第四组内置函数与高阶函数第9题请写出以下代码的输出结果nums [-3, 5, -1, 8, -2] result sorted(nums, keyabs) print(result)答案[-1, -2, -3, 5, 8]解析sorted函数的key参数指定排序依据这里用的是abs函数。所以排序不是按数值本身从小到大而是按绝对值从小到大abs(-1)1、abs(-2)2、abs(-3)3、abs(5)5、abs(8)8。这道题有两个考点一是abs内置函数的作用二是key参数接收的是一个函数对象而不是函数调用。很多初学者会写成keyabs()这是错的因为abs()是一个立即执行的计算传进去的是一个数字而不是排序用的函数。第10题下面的代码试图用循环创建一系列 lambda 函数输出结果是什么funcs [] for i in range(3): funcs.append(lambda: i) for f in funcs: print(f(), end )答案2 2 2解析这是 lambda 和闭包最经典的陷阱。lambda: i中的i并不会在定义时被立刻复制而是会捕获一个“引用”。当循环结束后全局变量i的值已经变成了2所以三个 lambda 函数在调用时读取到的都是同一个2。要修正这个问题可以把i作为默认参数传入funcs [] for i in range(3): funcs.append(lambda ii: i) for f in funcs: print(f(), end )这样每个 lambda 函数在定义时就把当前i的值绑定到自己的局部参数上了输出变成0 1 2。这类问题在 GUI 编程中特别常见比如你循环创建按钮给每个按钮绑定一个点击回调如果不处理闭包捕获问题最后所有按钮回调的都是循环变量的最终值。3. 我在批改中看到的高频易错点3.1 默认参数用可变对象是真实开发中最值钱的坑刚才第3题已经讲过def func(lst[])这种写法在面试中已经是“送分题”了因为大家基本都知道这里有问题。但在真实代码里它依然经常出现。我见过一个很典型的业务场景有人写了一个接收配置项的函数默认参数是一个字典def process_config(config{}): if timeout not in config: config[timeout] 30 return config第一次调用时一切正常第二次调用时如果没传config里面就已经带着上一次的timeout甚至是更早调用时添加的各种字段。如果程序里多个模块共用了这个函数就会出现“这次调用为什么带了上次的数据”这种非常诡异的问题。排查思路很简单如果函数默认参数是list或dict而且函数内部会修改这个参数那基本可以判定为踩了这个坑。修复方式也统一默认参数改成None函数内部重新创建。3.2 忘记写 return返回了 None 还不自知第2题那种情况在真实代码里也经常发生。很多人写完函数后只在函数内部写了print()没有写return然后在外面用返回值继续做运算结果得到的是None。这里我想强调一个经验在调试阶段print的确是最快的验证方式但在函数设计中尽量用return把结果交给调用方由调用方决定是打印、存储、还是转发。否则调试代码和业务代码会纠缠在一起以后要拆分困难得多。3.3 全局变量和局部变量同名结果就是个炸弹第7题那种UnboundLocalError是报错反而好排查因为你看到报错就知道了。更隐蔽的情况是你在函数内部读了一个全局变量的值却误以为它是局部变量或者反过来。我之前遇到过一个真实案例项目里有个全局配置变量叫DEBUG某个函数内部写了一个DEBUG DEBUG and log_enabled看起来是想把配置和另一个条件做个“与”运算并赋值。运行后直接报UnboundLocalError很多人第一反应是“我明明定义了 DEBUG 啊”。原因就是本文第7题讲的机制函数内部如果对某个变量进行了赋值Python 就把它当局部变量处理即使它和全局变量同名。代码一旦写到这种程度最简单的处理办法就是换一个局部变量名不要和全局变量重名比用global修修补补要干净得多。3.4 回调函数里传了“调用结果”而不是“函数本身”回调函数这个概念在 Python 里其实不复杂就是你把一个函数作为参数传给另一个函数等时机到了对方调用你传过去的函数。高频错误是写成了“调用”而不是“传递”。在sorted(nums, keyabs)里abs是传递函数对象abs()是调用函数并传入结果。两者的区别在命令式代码里经常被忽略但一旦走到回调场景就会立刻暴露。真实场景中给按钮绑定事件button.clicked.connect(run_task) # 正确传函数 button.clicked.connect(run_task()) # 错误传返回值第二种写法会在绑定阶段就立刻执行run_task()然后你把返回值传给了事件系统。如果返回值是None点击按钮就没有任何效果。4. 测验之外环境报错与函数调试实录4.1 “无法将 pip 识别为 cmdlet、函数”这类报错的排查思路在整理测验答案的时候我顺手看了下后台的搜索词发现大量同学卡在一个问题上在 PowerShell 或命令提示符里输入pip、git、python等命令提示“无法将‘pip’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”。这个问题虽然不直接属于“函数”的考点但它会直接阻止你在自己电脑上跑通函数练习。所以这里我多说几句排查思路。第一步先确认 Python 是否安装成功。在命令行输入python --version如果这个命令也报错说明 Python 本身都没装好或者装好后没有加入系统 PATH。Windows 安装 Python 时有一个Add Python to PATH的选项很多人安装时没勾选装完就找不到命令了。第二步如果python能用但pip不能用可以试试python -m pip --versionpython -m pip会直接通过 Python 解释器来运行 pip 模块不依赖 PATH 中的 pip 命令。这个写法其实是排查 pip 报错时最通用的手段。第三步查看自己的 PATH 配置。Windows 里在“高级系统设置”中找到“环境变量”检查 Path 里是否包含 Python 的安装目录和 Scripts 子目录。正常情况下安装 Python 后应该有类似这样的路径C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\ C:\Users\你的用户名\AppData\Local\Programs\Python\Python311\Scripts\看到“cmdlet、函数、脚本文件”这几个字很多人会慌其实它就是“命令找不到”的另一种表达方式。解决思路不是死记硬背某个命令而是把“命令是如何被找到的”这个逻辑搞定命令提示符会在当前目录和 PATH 列表目录里逐个查找可执行文件找不到就报这个错。4.2 函数调试三板斧print、logging、IDE 断点如果测验题里的代码在你自己电脑上报错了怎么快速定位我给自己的学生推荐的是三个工具按使用频率排序。第一是print调试适合快速确认函数走到哪一步了。比如def calculate(a, b): print(fa{a}, b{b}) result a b print(fresult{result}) return result在参数多、逻辑杂的函数里入口处打印一次入参返回前打印一次结果基本能判断问题出在函数内部还是外部。第二是logging模块适合正式点的项目。它的好处是有日志级别可以控制输出内容不像print一样只能删代码。调试阶段设成DEBUG正式运行设成WARNING不用改代码就能调整日志量。第三是 IDE 断点调试。PyCharm 或 VS Code 里在行号旁边点一下就能设置断点运行到那里就会暂停。这时候可以查看当前所有局部变量的值一步一步往后执行。这个方法排查复杂逻辑特别高效尤其适合那种“函数没报错但结果不对”的情况。4.3 一个实战排查案例我之前遇到过一个问题有人写了一个读写配置文件的函数本地运行正常但打包后在别的机器上报错提示FileNotFoundError。他没有在函数内部打印任何信息所以完全不知道函数在哪个环节崩掉的。排查流程是这样的。先在函数入口处打印传入的文件路径def load_config(path): print(fload_config called, path{path}) with open(path, r) as f: return f.read()运行后发现path参数是相对路径config/settings.ini而在打包后的程序里当前工作目录不是项目根目录所以相对路径指向了一个不存在的文件。这是一个非常经典的“函数本身没错调用方传参有问题”的案例。如果只看函数内部代码永远找不到原因只有在函数入口处把传入的参数打出来问题才浮出水面。这给我一个很重要的启示写函数的时候入参校验和入口日志不是可有可无的。哪怕只是临时加一行打印也能让问题排查快十倍。5. 从测验延伸出去好函数的三个审美标准5.1 单一职责一个函数只做一件事测验题里的函数都很短大部分只有几行所以“单一职责”体现得不明显。但真实项目里函数会越写越长。一个函数如果需要你分段阅读每段干的事情都不一样那它基本上已经违背了单一职责原则。判断标准很简单你能不能给这个函数起一个准确概括它全部行为的名字。如果你发现名字只能概括前半段行为后半段的内容完全没提那就说明该拆函数了。比如“读取文件并解析并写入数据库”这个函数三个动作拆成三个函数每个都能独立测试维护成本会低很多。5.2 输入输出明确不隐藏副作用好函数应该像一台接口明确的设备你给它明确的输入它给你明确的输出不在你不知情的情况下修改其他东西。测验中涉及全局变量和默认参数的问题本质上都是在讨论“副作用”。一个函数如果修改了外部全局变量或者修改了传入的可变对象调用者在使用时就要额外注意调用顺序和调用次数。这种隐形的依赖关系是产生诡异 bug 的重要来源。我的建议是函数能不用global就不用能不修改传入参数就尽量不修改。如果必须修改要在函数文档字符串里写清楚。5.3 复用与封装把重复代码收敛进函数函数最大的价值之一是复用。很多初学者在写代码时习惯把一段逻辑复制黏贴到多个地方。一旦后续需求变化就要在每一处复制的地方同步修改漏掉任何一处都会导致不一致。正确的做法是把重复逻辑抽成函数通过参数差异来应对不同场景。这个思路并不难但需要在写代码时刻意练习。我一般会建议学生如果同一段代码写了超过两次就停下来考虑能不能提成一个函数。5.4 给这套测验出“升级版”装饰器与生成器学完函数基础之后接下来可以挑战两个进阶主题装饰器和生成器。装饰器的本质是一个接收函数、返回新函数的函数它依赖的正是 Python “函数是一等公民”的特性。你之前见到的staticmethod、classmethod都是内置装饰器的应用。尝试自己写一个简单的计时装饰器是检验闭包理解程度的好方式。生成器则涉及yield关键字。它让你写出一个“暂停—继续”的函数适合处理大数据流。理解生成器之后你会更深刻地理解函数执行过程中“状态保存”是怎么实现的。如果这份模板测验的题目你都能一次性做对那么接下来把精力放到装饰器和生成器上收获会很大。我个人在实际批改中比较深的体会是函数这一章的测验分数高低并不代表你背了多少语法而代表你有没有建立起“把任务拆解成可调用单元”的思维习惯。很多人写不好函数不是不懂def怎么写而是没有想清楚函数边界在哪、参数怎么设计、返回值怎么约定。这份测验里的“坑题”恰恰就是逼你去思考这些边界问题。最后再分享一个小技巧碰到想不明白的函数问题先不要急着问别人。把你对代码的预测写下来然后运行对比预测和输出的差异。这个“预测—验证—反思”的循环是我见过的提升函数理解能力最快的方式。