尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python函数返回值return详解:从语法到爬虫数据处理实战

Python函数返回值return详解:从语法到爬虫数据处理实战 写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, encodingutf-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, headersheaders, timeout10) resp.encoding utf-8 return resp.text def parse_title(html): soup BeautifulSoup(html, html.parser) title soup.find(h1) return title.get_text(stripTrue) 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一个htmlparse_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 cleaneddata为空的时候函数直接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的那些坑跨语言对比更清醒热搜词里有一条“cint函数运行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, encodingutf-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(...))先看类型再看值基本两分钟就能定位问题。我自己调试了这么多年一直用的都是这个笨办法但真的很管用。
返回列表