Python函数返回值return详解:从语法到爬虫数据处理实战
2026/9/20 3:59:04 网站建设 项目流程

写Python的人,十有八九在刚接触函数的时候被return搞懵过。明明在函数里写了print,调用之后控制台也打印了东西,可一旦把这函数赋值给变量,拿到的却是None。随后翻教程、查博客,才明白print和return压根不是一回事。这个“成果交付”的动作,就是今天要聊的核心:Python函数返回值return。

这篇文章不只讲return的语法,还会把它放到真实项目里看,比如爬虫、数据处理、递归算法这些场景中return是怎么串联起整条逻辑链的。适合刚学完Python基础语法、开始写函数想弄明白return到底该怎么用的人,也适合那些写完代码不知道为啥返回值总不对的老哥。我尽量用最直白的方式把这件事讲透。

1. return到底是什么:函数成果的交付契约

1.1 把函数想成一条流水线:输入、加工、交付

我特别爱用流水线来类比函数。你在流水线一头塞进原料,机器在中间咔咔一顿加工,另一头吐出成品。这个“吐出成品”的动作,对应的就是return。

注意这里的关键词是“吐出成品”。如果你只在机器里装了个喇叭,加工完了喊一嗓子“我做完了”,那机器本身并没有把任何东西递到你手上。这个喇叭,就是print。

好多新手写代码是这样:

def add(a, b): result = a + b print(f"结果是{result}") total = add(3, 5) print(total) # None

第一段代码运行后,你会看到控制台输出“结果是8”,但第二行打印total却是None。原因就是add函数用print把结果亮了出来,但压根没有return,所以函数默认返回None。

网上经常有人把“函数有没有输出”和“函数有没有返回值”混在一起。其实两者的区别就一句话:print是给控制台看的,return是给调用代码用的。当你把这个函数当作一个“零件”嵌进更大逻辑里的时候,没有return的零件就相当于断供了——你拿不到成品,后续工序全部干不了。

1.2 return的类型决定你能拿到什么

Python是动态类型语言,函数的返回值类型不像C++或者Java那样写死。同一个函数可以返回整数、字符串、列表、字典,甚至是一个函数对象。这种灵活性是优势,但也容易埋坑。

举个例子。你写一个读取配置文件的函数,正常情况下返回一个字典:

def load_config(path): with open(path, "r", encoding="utf-8") as f: lines = f.readlines() config = {} for line in lines: key, value = line.strip().split("=") config[key] = value return config

但如果文件格式错了,或者读取失败,你没做异常处理就直接写了return,那这个函数可能返回一个空字典,也可能抛出异常。回到调用方,有没有对返回类型做校验,能不能区分“正常结果为空”和“读取失败”,这就是成熟代码和玩具代码的分水岭。

经验之谈:写函数的时候,最好在文档字符串里写清楚返回什么类型、什么情况下返回什么。别嫌啰嗦,等三个月后你自己回来看这段代码的时候,你会感谢当时的自己。

2. return的核心玩法:从单值到多值,从判断到提前退出

2.1 多值返回与解包赋值:一个return交两样东西

很多从C、Java转过来的人,第一次看到Python的多值返回会觉得神奇:一个函数怎么能同时返回好几个值?其实本质是返回了一个元组(tuple)。

def get_min_max(numbers): return min(numbers), max(numbers) low, high = get_min_max([3, 1, 4, 1, 5, 9, 2, 6]) print(low) # 1 print(high) # 9

这里return min(numbers), max(numbers)看起来返回了两个值,实际上Python把这两个值打包成了一个元组(1, 9),等号左边的low, high再把这个元组解包赋值到两个变量上。

这个技巧在写算法题的时候特别常见,比如查一个数组的最大值和最小值、同时返回商和余数、同时返回索引和值。你不需要定义一个专门的类或者结构体来“装”结果,一个return全给你搞定。

但要注意一点:解包赋值要求左边的变量数量和元组长度完全一致,不一致会直接报ValueError。如果你只想要其中一个返回值,可以用下划线忽略:

max_value, _ = get_min_max(numbers)

2.2 提前return:卫语句让代码少一层嵌套

return不光是函数结尾处交成果,还可以在函数中间提前走人。这个写法有个专业名词叫卫语句(guard clause)。

看这个例子,一个检查用户输入是否合法的函数:

def validate_input(data): if not isinstance(data, str): return "类型错误" if len(data) == 0: return "内容为空" if len(data) > 100: return "长度超限" return "OK"

看到没有,每一层检查不满足条件就提前return,根本不需要一层层if嵌套。代码读起来是线性的,逻辑清晰多了。如果你把所有检查都套在if里面,写出来就是这种“箭头形”代码:

def validate_input(data): if isinstance(data, str): if len(data) != 0: if len(data) <= 100: return "OK" else: return "长度超限" else: return "内容为空" else: return "类型错误"

这种嵌套能把你恶心到怀疑人生。后期改动一个条件,括号匹配都能搞半天。提前return的价值在复杂逻辑里会被无限放大。

2.3 bool函数与真值判断:把判断变成一句话

搜索“bool类型函数的返回值”,显然很多人都遇上了这个场景。其实在真实项目中,大量函数的存在不是为了返回什么计算结果,而是为了回答一个问题:这个条件成立吗?

这类函数叫谓词函数,经典命名是is_开头(is_valid、is_ready、is_empty)或者has_开头(has_permission、has_key)。

def is_prime(n): if n <= 1: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return True

这个函数返回的只有True或False。调用的时候也特别自然,直接当条件用:

if is_prime(17): print("是质数")

注意,这种函数里面不要写print。return True表示“答案是是的”,调用方拿到这个布尔值自己去决定下一步干什么。你要是顺手在里面print了一个“是质数”,那这个函数就废了——又当判断又当输出,别人想复用它来做别的事情就难受了。

还有一个Python特有的点:真值判断。有些代码你看到的是return一个对象而不是布尔值,例如:

def find_user(users, name): for user in users: if user["name"] == name: return user return None

这个函数在找不到用户的时候返回None。调用方写if result:就能判断有没有找到,因为None在布尔上下文里是False,找到了就是非空字典,是True。这种写法也有人在用,但有个前提:调用方必须知道“空值”长什么样,否则容易把空列表[]、空字符串""这些“假值”误判成没有找到。严谨的做法还是明确返回布尔值或抛异常。

3. 实战:让return在真实项目里帮你交付成果

3.1 爬虫函数:return的数据就是下一道工序的原料

爬虫是Python最热门的应用方向之一。你看搜索词里也有python爬虫、python爬虫教程。写爬虫的时候,函数设计最核心的一点就是:每一步都要把数据return出来,交给下一步。

一个典型的爬虫流程大概分成三步:请求页面、解析数据、保存结果。我写过一个简化版:

import requests from bs4 import BeautifulSoup def fetch_html(url): headers = {"User-Agent": "Mozilla/5.0"} resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" return resp.text def parse_title(html): soup = BeautifulSoup(html, "html.parser") title = soup.find("h1") return title.get_text(strip=True) if title else "未知标题" def main(): url = "https://example.com" html = fetch_html(url) title = parse_title(html) print(title)

fetch_html返回的是网页源码字符串,parse_title接收这个字符串,解析完再返回标题。每一层函数都只负责一件事,而且一定把成果通过return交出去。如果fetch_html里面自己print一个html,parse_title根本拿不到东西,整个链路就断了。

这就是return在真实项目里最核心的作用:它是函数之间传递数据的通道。print只是调试用的辅助工具,在最终main函数里把所有环节串联起来时,最后一次print或者保存文件才能看到最终成果。

3.2 递归与算法:return把深层结果送到顶层

递归是return特别容易用错的地方,因为一个递归函数的过程就是层层调用,每层都要return,最后把结果一层层传回来。

以经典斐波那契数列为例:

def fib(n): if n <= 1: return n return fib(n - 1) + fib(n - 2)

这里最关键的是fib(n - 1)和fib(n - 2)这两个子调用的返回值必须向上返回,和当前层相加,再return出去。如果你只是调用不算返回值,那这层函数返回的就是None,一层的None往上叠加,最后整个函数什么都得不出。

再比如动态规划里的01背包问题,需要返回最大值。我写一个自顶向下的记忆化版本:

def knapsack(weights, values, capacity): n = len(weights) memo = {} def dfs(index, remaining): if index == n: return 0 if (index, remaining) in memo: return memo[(index, remaining)] # 不装当前物品 best = dfs(index + 1, remaining) # 尝试装当前物品 if weights[index] <= remaining: best = max(best, values[index] + dfs(index + 1, remaining - weights[index])) memo[(index, remaining)] = best return best return dfs(0, capacity)

注意这个内嵌函数dfs里,所有分支都要return。递归函数有一个铁律:只要一个分支漏了return,那个分支返回的就是None,整个链路的计算结果就会出错。我踩过太多次这种坑了,排查起来累死人。

3.3 数据清洗与类型转换:return做“格式化交付”

搜索词里有python类型转换、python数据分析与可视化,这里也想多说一句。数据处理里,函数经常要做的是“把输入转成另一种格式再返回”。比如从爬虫抓下来的数据都是字符串,需要转成整数或者浮点数,就需要一个清洗函数:

def clean_number(value): if value is None: return 0.0 try: return float(str(value).replace(",", "").strip()) except ValueError: return 0.0

这个函数把“可能有逗号的字符串”、“带空格的字符串”、“None值”全部统一成float返回。下游做统计、算均值、画图,拿到的数据格式一定是干净的。

写这类函数的时候,尤其要注意返回值类型的一致性。有些人不小心会写成一个正常时候返回float、异常时候返回字符串,这种代码能坑死人。数据量大一点,你根本不知道哪一行数据触发了分支,等到画图的时候突然报错,排查半天才发现是数据类型不统一。

4. 常见问题与排查技巧:为什么你的return不生效

4.1 把print当return:最常见的“假不生效”

几乎每个学Python的人都会遇到“我只做了print,没做return”的情况。但奇怪的是,代码不报错,结果看起来也对,就是你赋值给变量之后拿不到值。

排查方法很简单:把函数调用的结果print出来看看到底是什么。

result = your_function(x) print(result)

如果是None,那就别怀疑了,函数里肯定只print没return。去函数体里看最后一个操作是不是print,如果是,改成return。

这里给一个经验判断法:你写这个函数的时候,脑子里想的是“我要让调用的人看到结果”,还是“我要让调用的人拿到结果”?想让人看到,用print;想让人拿到,用return。调试阶段print看两眼没问题,但正式的逻辑传递,必须return。

4.2 return后面的代码不执行:提前结束的玄机

return还有一个特性是“一旦执行,函数立刻结束”,return后面的代码一辈子都不会跑。这个很多人知道,但真正踩坑的是你在分支里提前return了,后面的收尾代码没执行。

比如这样:

def process_data(data): if not data: return # 下面是处理逻辑 cleaned = clean(data) save_to_db(cleaned) return cleaned

data为空的时候,函数直接return,后面的保存操作全部跳过。这本身逻辑没问题,但如果你在后面加了一行日志打印或者统计调用次数,就要注意它会不会被提前return跳过。实际项目里,我喜欢把“不论什么情况都要执行”的代码放在try/finally里:

def process_data(data): log_start() try: if not data: return None cleaned = clean(data) save_to_db(cleaned) return cleaned finally: log_end()

finally里的代码是无论是否return都会执行的,适合放日志、释放连接、关闭文件这类必须执行的收尾动作。

还有一个狠一点的细节:如果在finally里面写了return,它会覆盖掉try里面的return值。这是Python的一个大坑,我建议你永远不要在finally里写return,宁可多用几个变量把结果先存住。

4.3 环境与配置问题:有些报错根本不是return的锅

搜索词里还有几个看着meta痛的,比如“error: config must export or return an object”、还有“python was not found; run without arguments to install from the microsoft store”,以及“loadstring(utf8.char(...table.unpack({108,111,97,100,115...)))”这种。这些其实不是return语法本身的问题,而是环境或者配置层面的问题,但新手很容易误以为是自己return写错了。

比如那个config must export or return an object,常见于rollup或者esbuild这类打包工具配置场景。它的意思是配置文件必须导出一个对象,你如果只写了一堆代码没漏掉module.exports或者export default,就会报这个错。你看,这里虽然是JavaScript生态,但背后的思想跟Python的return是一样的——你写了一个“函数”,但没有“交付成果”。

至于“python was not found; run without arguments to install from the microsoft store”,这纯粹是Windows环境没装Python或者没配好环境变量。还有“unexpected license problem; exiting. hit return to exit”这种,是某些闭源工具在License校验失败时的提示。这几个问题本质上和函数返回值无关,但确实会卡住新手好几天。

我的建议是:如果你在VS Code或者PyCharm里点击运行,结果控制台提示python not found或者类似问题,先检查环境变量和Python解释器路径,别一头扎进去改代码。代码写得再对,环境没配上,return就是“不生效”。搜索词里还有vscode python环境配置、pycharm配置python环境、安装python这类,基本都指向同一个核心:确保你在终端里输入python或者python3能启动解释器,再用同样的解释器路径配置IDE。

4.4 C语言里return的那些坑:跨语言对比更清醒

热搜词里有一条“c++int函数运行return无法正常返回”,顺便聊一下。C语言里,如果一个int函数没写return或者走了一条没return的路径,函数返回的值是未定义的,通常是寄存器里的旧值。这种代码在编译器警告级别高的时候会提示“control reaches end of non-void function”,但会继续编译运行,结果可能是个随机数。这跟Python的行为完全不同——Python如果没return,默认返回None,行为是确定的。而C眼里,没返回就是一堆垃圾值。

所以我一直理解不了为什么还有人在网上争论“函数到底要不要写return”。在Python里,你可以写一个什么都不返回的函数(返回None),这在面向过程的场景里很常见(比如只为了副作用修改外部状态)。但在C/C++里,非void函数不写return属于未定义行为,必须避免。

跨语言的对比最能帮助你理解return的本质:return就是把一个值从被调用函数的“内部空间”传递到调用方的“外部空间”。语言不同,只是传递规则和默认值不同,核心思想完全一样。

5. return之外:生成器yield,另一种“交付”方式

聊return,顺便提一嘴它的兄弟yield。有时候你会发现,某些函数返回值很多,多到一次性全部算出来放内存里扛不住。这时候就该用生成器了。

def read_large_file(file_path): with open(file_path, "r", encoding="utf-8") as f: for line in f: yield line.strip()

这个函数没有return,或者说,它每次执行到yield都会“暂时交出一个值”,然后函数状态被挂起,下次再接着往下走。调用方可以像这样遍历:

for line in read_large_file("big_data.txt"): print(line)

对处理超大文件来说,yield比return一次性返回所有行要优雅得多。因为它不需要在内存里攒一个巨大的列表,而是一行一行地处理。很多新手不知道这个区别,一上来就用return一个列表,结果读几百MB的文件直接内存爆炸。

所以,如果你写函数的时候发现“我要返回的数据量可能很大”,先想一想是不是改成生成器更合适。return适合“一次性交付全部成果”,yield适合“边生产边交付”。

6. 关于return代码风格的三条经验

最后分享一点我在实际项目里总结出来的return使用习惯,算不上什么高深理论,但真的能少踩坑。

第一条:一个函数尽量只有一个出口,或者统一用卫语句在入口处做检查。这个看似矛盾,其实不矛盾。如果是简单函数,我倾向于“计算结果在最后统一return”,这样逻辑是一条直线。如果是复杂函数,参数检查、权限校验这种前置条件用提前return,核心逻辑走最后的单出口。

第二条:return的返回值类型必须稳定。你可以不写类型注解,但心里要门儿清。一个函数今天返回数字,明天返回字符串,后天返回None——这代码早晚变成定时炸弹。就算临时要改,也别直接改返回类型,宁可新写一个函数,也好过在一个函数里变来变去。

第三条:拿return的结果做下一步操作之前,先想清楚边界情况。数据为空怎么办?类型不对怎么办?网络请求失败怎么办?这些边界情况对应的return值,一定要在写函数的时候就想好。别等到线上跑挂了再回来补。我见过太多人只关心“正常路径”,从来不想“异常路径”,结果就是排查bug的时间比写代码的时间多十倍。

写在最后

找了半天也没找到什么几个字的万能结论。Python函数的return就是一个很朴素的动作:把你算好的结果交给调用你的人。但这个朴素动作背后,是关于代码结构、数据流、项目稳定性的思考。如果你能把函数当成一个真正的“交付者”——明确自己交付什么、用什么格式交付、边界情况交付什么——那你的代码水平会一下子往前跨一大步。希望这篇文章能帮你在return这件事上彻底通一回。

最后再补一个小技巧:调试的时候实在搞不清一个函数返回了什么,可以直接在调用处加一个print(type(...)),先看类型再看值,基本两分钟就能定位问题。我自己调试了这么多年,一直用的都是这个笨办法,但真的很管用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询