如果你也在跟着《笨办法学python》这本书学Python,做到第21课,请先停一下。这一课讲的是函数的返回值,就一个return关键字,看起来简单到不值一提,却是无数初学者的分水岭。很多人前面练得顺风顺水,到第21课突然开始晕:函数里的print明明能把结果打出来,为什么还要return?返回值到底返回给谁?如果函数没有return,结果又是什么?这篇内容我就是想用这本书的“笨办法”思路,把函数返回值这件事彻底讲透,同时带着你把手写代码、手算结果、改代码做实验整个流程走一遍。读完之后你会很清楚地理解return,也就算正式迈进了“用函数组织逻辑”这道门。不管你是刚装好Python还没开始学,还是学到函数这章卡了壳,这篇都适用。
1. 为什么“笨办法”学函数反而最扎实
1.1 第21课的定位:从“打印结果”到“返回结果”的思维转变
《笨办法学python》这本书开局就说了,学习编程最好的方式是动手写代码,而且是大量地、重复地写。很多人觉得“笨办法”就是傻乎乎地敲代码,其实它真正的意思是:先让你的手和脑建立直觉,再谈理解。第21课正好卡在一个转折点。前面几课,你学会了用print把内容打印到屏幕上,学会了定义函数、给函数传参数,知道了def、缩进、冒号这些基本的语法结构。到了第21课,作者开始教你让函数“产出”一个结果,而不是仅仅“打印”一个结果。
你可以先花一分钟想想Excel里的公式。比如=SUM(A1:A10),它的作用不是把结果“贴”在某个单元格里给你看一眼,而是当你把它放进另一个公式里计算时,它还能继续参与运算。这个“放进另一个公式里继续参与运算”的能力,在Python里就是靠return实现的。
这就是第21课的定位:它教你把函数从“只会喊话的喇叭”变成“能交作业的机器”。喇叭就是print,作业就是return。喊完话流程就结束了,而交完作业,后面的代码还能拿作业继续做事情。很多人没转过这个弯,是因为前面练习里print用得太顺手了,看到屏幕上出内容就以为任务完成。实际上,屏幕上的内容和程序内部能使用的数据,是两回事。这一课就是要把这两件事彻底分开。
1.2 这套思路适合谁、能解决什么问题
先说适合谁。第一种,零基础刚接触Python的,前面语法看完了,但不知道函数返回值和普普通通的打印有什么区别;第二种,学过一段时间的,之前都是照着教程抄代码,到了抽象概念比如return这里开始犯迷糊,做题总是报错;第三种,正在带人学Python或者做培训的,想找一个能把第21课讲明白、可复现的教学素材。
它解决的问题也很集中:搞不清return到底返回到哪里、print和return能不能混着用、函数没有返回值的时候执行结果是什么、以及返回值为什么会参与后面的计算。这几个问题如果没弄通,后面学列表、字典、文件读写、面向对象的时候,会一直被卡住。因为Python里几乎所有的“结果”都是通过return吐出来的。
在动手之前,先把环境准备好。我默认你已经装好Python了,装过的人跳过这段。还没装的话,最简单的方式就是去python.org官网下载对应版本的安装包。Windows用户安装的时候务必勾选“Add Python to PATH”这一项,不然命令行里会经常提示“python was not found”,这是很多新手第一个被劝退的地方。Linux用户一般直接用系统的包管理器装python3就可以,比如apt install python3。想用Docker跑Python环境的,可以去搜一下ubuntu安装docker并运行python环境这类教程,但说实话,现阶段没必要搞那么复杂。编辑器方面,新手用VS Code装一个Python插件就够,也可以直接用PyCharm Community版,一直免费。我的建议是:别纠结工具,哪个顺手用哪个,装了能跑Python就赢了。
2. 第21课核心细节:返回值与函数的三个关键点
2.1 函数定义里的参数、函数体与return
先把函数的长相拆开看。一个典型函数长这样:
def add(a, b): print(f"ADDING {a} + {b}") return a + b这里def是关键字,add是函数名,括号里的a和b是参数,冒号下面缩进的代码是函数体。函数体里print和return这两行都属于它。当你调用add(30, 5)的时候,30会传给a,5会传给b,然后函数体从上往下执行。
关键在这里:return是函数体的出口。执行到return这一行,Python会先计算return右边的表达式的值,然后把结果从函数里“递出来”,并且整个函数立刻停止。不管return下面还有多少行代码,都不会再执行了。这一点和print非常不一样。print只是把文字显示在屏幕上,函数还会继续往下走;return则是带着值直接退出。
我经常用一个自动售货机的类比:函数名是售货机的位置,参数是你投入的硬币和按下的按钮编号,函数体是机器内部的机械结构,return是从出货口弹出来的饮料。饮料被取走之后,机器这一轮的运行就结束了。如果你的机器没有return,那就像你按完按钮,机器哼唧了一会儿,却什么都没从出货口弹出来——表面上看起来它工作了一下,但你没有拿到任何东西。
2.2 print和return的本质区别(含示例代码)
这个区别值得用代码做实锤。我直接给你一个最简单的小实验,你可以在自己的电脑上跑:
def print_result(): print(42) def return_result(): return 42 a = print_result() b = return_result() print("a的值:", a) print("b的值:", b)运行结果非常有意思:
42 a的值: None b的值: 42第一行42是print_result内部print出来的。a被赋值为None,因为print_result只有print,没有return。b被赋值为42,因为return_result实实在在返回了42。None是Python里的一个特殊值,表示“什么都没有”。记住这一点,后面排查问题会经常用到。
那如果函数里既写了print又写了return呢?那就两个效果同时发生:屏幕上能看到打印的内容,调用处也能拿到返回的值。第21课默认的add函数就是这种设计,它先打印ADDING 30 + 5,再返回35。这样学习者既能清楚地看到函数执行的过程,又能拿到结果继续用。这个设计非常巧妙,不是作者随手写的。
我再补一个对比:如果你把一个带print的函数的调用结果当成参数传给另一个函数,会怎样?比如print(multiply(2, 3)),先把multiply(2, 3)算出来拿到6,再交给print,打印出6。但如果你把print(2 * 3)当成一个值,那它返回的是None。很多人在这里写print(print(6)),结果看到首行6,然后是None,这就是print的返回值在捣乱。这类小实验做上两三次,return和print的区别就刻在脑子里了。
2.3 多个返回值、返回None与类型转换
第21课的return a + b看起来简单,但它背后藏着一个规律:return右边可以是任何表达式。所谓表达式就是能被计算出一个值的写法,比如a + b、a - b、a * b、a / b,还有调用另一个函数的表达式,比如multiply(2, 3)。函数执行到return时,会先把右边表达式的值算出来,然后把这个值返回。
那函数能不能没有return?能。如果你只写函数体、不写return,函数执行完之后会自动返回None。有些函数(比如一个只负责打印报表的函数)确实不需要返回值,这时返回None完全正常。但如果你期望它返回计算结果却忘了写return,问题就来了——后面拿到的全是None,任何数字运算都会报错。判断一个函数到底有没有返回值,最简单的办法就是打印函数调用结果,看到None就知道漏了return。
这里再提醒一个和第21课强相关的类型问题。Python里input()读进来的东西永远是字符串,你看代码里add(30, 5)的30、5是数字,如果你从键盘输入“30”和“5”,这两个东西在Python眼里是字符串,不是数字。字符串做加法得到的是拼接,比如“30” + “5”是“305”,而不是35。所以一旦你想写一个“用户输入两个数,把它们相加”的程序,必须在input外面套上int()或float()转换,否则结果会让你怀疑人生。
这也就是为什么我总说“类型转换”是新手避不开的坎。搜索引擎里天天有人搜python类型转换、python定义函数、python基础语法,其实它们的交汇点就在第21课这个位置:函数接收参数,函数体里做运算,return返回结果。返回结果之前,你得确保这个结果是正确的数字类型。
3. 手把手实操:完成第21课的加法、减法、乘法、除法练习
3.1 完整代码与逐行讲解
现在我们把书里的代码完整跑一遍。建议不要复制粘贴,哪怕慢一点,也要自己一个字一个字地敲一遍。敲代码的过程就是在建立手感,这是“笨办法”的精华。
def add(a, b): print(f"ADDING {a} + {b}") return a + b def subtract(a, b): print(f"SUBTRACTING {a} - {b}") return a - b def multiply(a, b): print(f"MULTIPLYING {a} * {b}") return a * b def divide(a, b): print(f"DIVIDING {a} / {b}") return a / b print("让我们用函数做点数学题吧!") age = add(30, 5) height = subtract(78, 4) weight = multiply(90, 2) iq = divide(100, 2) print(f"年龄: {age}, 身高: {height}, 体重: {weight}, IQ: {iq}") print("这是一个谜题。") what = age + height - weight * iq print("结果是: ", what)我一行一行拆一下。def add(a, b): 这一行定义函数,a和b是形参。下一行print打印了一个提示,只是让运行过程更直观。return a + b是函数的核心:把a和b相加的结果作为函数的返回值。后面三个函数结构一样,只是运算符号不同,subtract做减法,multiply做乘法,divide做除法。第21课故意把这四个函数写得很对称,就是为了让你在重复中建立“参数进来、结果出去”的肌肉记忆。
到了调用部分,age = add(30, 5)这行值得盯三秒钟。执行这行时,Python会跳到add函数,让a=30、b=5,先打印ADDING 30 + 5,然后计算30 + 5得到35,return 35,最后把35赋给age变量。整个过程里,age拿到的不是“打印出来的文字”,而是实实在在的整数35。
这是很多人的认知盲区:看到add函数里有个print,就以为age会变成“屏幕上显示的那句话”,不是的。print只是给观众看的字幕,return才是真正交付的货物。所以后面打印f"年龄: {age}"时,显示的是35,而不是“ADDING 30 + 5”。这个例子把print和return放在同一个函数里对比,真的太适合新手理解了。
3.2 运算结果的“组合拳”:函数返回值作为另一个函数的参数
书里最精彩的部分在谜题这里。当你有了age、height、weight、iq四个变量,后面就有了这行:
what = age + height - weight * iq
这一步妙就妙在,你把一个个函数的返回值当成了普通数值来参与运算。这不就是在用积木搭东西吗?每个函数是一块积木,return让积木之间能咬合在一起,而print不能咬合。
先手算一下答案。把各变量代入:
age = add(30, 5) = 35
height = subtract(78, 4) = 74
weight = multiply(90, 2) = 180
iq = divide(100, 2) = 50.0
注意最后一个是浮点数50.0。Python里整数除以整数常常得到浮点数。那weight * iq就是180 * 50.0 = 9000.0。到这里你可能会问,为什么不是先算age + height?因为Python的运算优先级和数学一样,乘法先于加减法。所以这个表达式实际上等于35 + 74 - (180 * 50.0),先算9000.0,再算35 + 74 = 109,最后109 - 9000.0 = -8891.0。这就是第21课谜题的经典答案。
你运行时会发现,程序先疯狂打印了一堆ADDING、SUBTRACTING,然后把-8891.0打出来。很多初学者在屏幕上看到这个负数会愣住,因为正常人的直觉是从左往右算:35 + 74 = 109,109 - 180 = -71,-71 * 50 = -3550。如果算出-3550,并且和程序输出的-8891.0对不上,那恭喜你,你已经开始认真思考运算优先级了。
我再延伸一个非常实用的用法:函数返回值可以直接作为另一个函数的参数。比如你想写add(10, multiply(3, 4)),执行顺序是先算内层multiply(3, 4)得到12,再算外层add(10, 12)得到22。这种嵌套调用的存在,完全是因为“函数调用表达式本身有值”。没有return,这种组合拳就打不出来。等以后你写数据分析、写Excel处理脚本、写爬虫,到处都会用到这种嵌套结构,现在打好底子特别值。
3.3 修改练习:把return改成print会发生什么
光看懂还不够,我强烈建议你做一次“破坏性实验”:把add函数里的return改成print,再跑一遍原来的代码,看看会发生什么。
def add(a, b): print(f"ADDING {a} + {b}") print(a + b) # 原来是 return a + b这时跑主流程,你会看到ADDING 30 + 5这一行照常打印,下一行多了一个35,但再往后,程序在what = age + height - weight * iq这一行报错。报错信息大致是:
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
翻译过来就是:NoneType和int不能相加。为什么?因为age = add(30, 5)时,add没有return,age被赋成了None。None是“什么都没有”,当然不能和整数做加法。
这个实验能帮你建立两个重要的直觉。第一,光有print,函数调用处什么值都拿不到;第二,报错信息虽然吓人,但看懂关键字NoneType就能定位到问题:某个函数忘了return。我见过太多人一看到TypeError就慌,其实这恰恰是Python在告诉你,它拿到的类型不对。
排查的时候,你只要在报错之前加一行print(age),看到输出None,立刻就知道问题出在add函数上。这个调试习惯从第21课开始养成,以后能省无数时间。做完实验记得把代码改回来,不要留着错误的版本。我还试过另一种改法:把return留下,把函数里的print全删掉。这时运行程序,没有任何过程提示,但四个变量的值依然正确,最后谜题照样输出-8891.0。这进一步证明,过程的“字幕”可有可无,真正的“货物”是return送出去的。
4. 常见问题与排查技巧实录
4.1 初学者最容易踩的5个坑
我把这段时间反复看到的高频问题整理了一下,做成一个速查表,你遇到问题直接对号入座。
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| 函数调用结果总是None | 函数里忘了写return,或者return写在了永远不会执行到的位置 | 检查函数体的结尾是否有return;把print调试加在return之前 |
| print(f"ADDING {a} + {b}") 打出来的是变量名而不是数值 | 字符串前面漏了f | 确认字符串前有f前缀,f字符串里要放{}包裹的变量 |
| IndentationError: unexpected indent | 缩进不一致,函数体某行多了空格 | 统一用4个空格缩进,不要混用Tab和空格 |
| TypeError: unsupported operand type(s) for + | 参与运算的值里有None,或者有字符串和数字混加 | 打印每个变量的值和type(),检查是否忘记return或忘记类型转换 |
| 返回值只有一个结果,循环里后续数据丢了 | return写在循环内部,第一次循环就退出了 | return是函数出口,如果要在循环里产出多个结果,先收集到列表里再统一返回 |
这5个坑其实都指向同一个本质:你还不完全清楚函数在什么时候结束、返回什么。前面那个“售货机”的类比能帮你一半,另一半就靠报错信息来对答案。报错信息不是敌人,它是最诚实的提示。
我再补一个实操建议:学第21课的时候,每改一次代码,就运行一次,观察输出变化。不要一次写一大段然后运行,那样报错了你不知道是谁的锅。写一段、跑一段、看一段,这种小步快跑的方式,对新手是最友好的。我自己早期学Python就吃过这个亏,一口气写了几十行,报错之后来回找了半天才明白是第三行少了个括号。
4.2 怎么判断函数到底返回了什么
判断函数返回值的方法其实就那么几种。第一种,直接print(函数调用()),比如print(add(2, 3)),如果打印出5,说明返回了5;如果打印出None,说明这个函数根本没返回任何东西。第二种,赋值给变量后用type()看类型,比如r = add(2, 3),然后print(type(r)),看到<class 'int'>就知道是整数,看到<class 'NoneType'>就知道没有返回。
第三种最实用:临时在return之前加一行print看看数据。比如你怀疑multiply函数算错了,可以在return a * b前面加一行print("debug a:", a, "debug b:", b),看看参数到底是多少。如果a传进来是个字符串"9",而你以为是数字9,那后面一切计算都会跟着错。
我想强调,调试不是浪费时间,而是学编程的基本功。第21课本身代码很短,正适合用来练手。你把每一处print都当成探针,就能看清函数内部的数据流。等以后你做爬虫、做数据分析,一个脚本几百行,没有调试能力寸步难行。现在把“打印中间值”这个习惯练好,是稳赚不赔的。我见过太多人遇到问题只会盯着代码发呆,其实解决方案已经在眼前了:加一行print,看看到底是什么。
4.3 学习这本书的节奏建议(以及后续怎么走)
最后聊聊节奏。《笨办法学python》这套书最强调的就是节奏感:每天一个练习,不求多,但求每个练习都亲手敲完、把附加题做了。第21课这种内容,尤其适合多折腾。我做这几组实验的顺序供你参考:先原样跑一遍第21课;然后把return改成print,观察报错;再把print删掉、只保留return,观察屏幕没有提示但结果正常;最后写一个自己设计的函数,比如输入身高体重返回BMI,或者输入温度返回华氏度转换结果。把这个过程做完,你的函数基础就算是结实了。
我顺手给你一个BMI函数的小例子,这是非常好的巩固练习:
def bmi(height_m, weight_kg): return weight_kg / (height_m ** 2) my_bmi = bmi(1.75, 70) print(my_bmi)这个例子用到了return、参数、表达式和浮点数计算,和第21课完全是同一个套路。你自己再改成一个摄氏温度转华氏度的函数:return celsius * 9 / 5 + 32。写完之后你会觉得,原来函数返回值就是“把计算结果交给调用者”,没有想象中那么玄。
做完第21课后,你离那些热门玩法其实不远了。搜索引擎里那些'python爬虫'、'python数据分析与可视化'、'python写入excel'、'python量化交易策略代码',底层逻辑都建立在函数处理数据的能力上。你能理解return,就等于理解了函数之间怎么传递数据处理结果,后面学库、学框架会顺畅很多。
我个人还有个习惯:每学完一个练习,把当天敲的代码文件按编号存好,比如ex21.py、ex21_test.py,方便后面复习。这本书的练习都是固体积累,等看到第40课时再回头看第21课,你会感慨当初那点纠结特别值得。
我自己做第21课时,也被那个-8891.0折磨过。当时我算了三遍都算不出这个数,最后一查才明白自己是按从左到右的顺序瞎算的,把运算优先级抛到脑后了。后来我把书里的函数自己改编了几个,又反复做了把return改成print的实验,才真正想通return和print的区别。现在回头看我可以说,函数返回值这堂课,值得你慢一点、笨一点地去学。多敲几遍,多做几个小实验,比你看十篇教程都有用。