没写 return 的函数,会自动给你一个 None。

这话一点不夸张:你写了个函数,里面啥也没 return,或者根本没写 return,调用的时候就会得到一个特殊的东西,名字叫 None。这个是语言的设计,Python 明清楚白地把“没意义的返回”用 None 表明。别慌,也别怪自己,问题只有在你想用返回值去做判断或运算的时候才会冒出来。
先说几个常见的坑和正确做法,讲清楚比死记硬背规则有用。许多人刚学到这儿就被绊住:写了个函数只想看输出,就用 print,然后在别处想再利用这个“输出”,结果发现拿到的是 None。print 是给人看的,return 才是给代码用的。打个比方,print 是把东西放到桌子上让人看,return 是把东西装进包里带走。想带走就得用 return,不然只能站在桌前盯着看。
关于 None 的判断要讲究点技巧。不要用 == 来判断一个对象是不是 None,应该用 is 或 is not。缘由很简单:== 是比较值,背后可能调用 __eq__,行为跟你想的不完全一样;is 比较身份,判断对象是不是“真·None”。这点在团队代码里尤其重大,防止出现奇怪的比较结果。
参数传递也有几条好用的规则。位置参数按顺序传,关键字参数可以按名字传,关键字参数必须放在位置参数的后面。写错顺序,Python 会直接报错,挺好,直接告知你别混淆。还有默认参数的老坑:如果你把可变对象(像列表、字典)作为默认参数,多个调用之间会共享同一个对象,导致数据越积越多、行为越来越奇怪。典型的修复办法是把默认值设为 None,函数内部再按需要创建新对象。示例长得就像这样:参数写成 lst=None,函数里写 if lst is None: lst = [],接着再操作 lst,这招几乎是防坑必备。
要同时返回多个值,别去打印一堆东西再指望能拿回来。直接用 return 返回多个值,Python 会把它们打包成元组自动返回。然后在调用处可以用 a, b = func() 来解包,用起来干净利落。如果你偏爱列表或字典,也可以自己返回它们,按你需要来组织数据。别把调试时的打印当成“返回”,那是两码事。
再说点细节,方便实战用。函数名别起得太抽象,能从名字看出它干啥就行。参数名也别省略注释,当函数越来越多、参数越来越多时,关键字参数能让调用处更清楚。写接口的时候,合理利用关键字参数和默认值,能让函数更友善,但别把可变对象当默认值那是老坑,别掉进去。
有些内置函数用得多,理解了会事半功倍。列如 range(10) 会给你 0 到 9 的序列,这种“右开区间”的设计在许多地方都能遇到,理解了就少犯错。还有 *args 和 **kwargs,实在传不定参数的时候用,调用方和实现方都更灵活,别滥用,越清晰越好。
调试时的一些小技巧也能省时间。遇到 None 报错,先检查函数有没有 return,或者检查调用链上是不是把返回值覆盖成了 None。写单元测试能早早抓住这些问题,简单的断言就能发现返回值是不是你想要的类型。平时写代码习惯用断言和针对边界情况的测试,调试效率会上来。
我见过不少人被默认参数的共享问题整得头大:函数看着没问题,多个调用结果却混在一起,追了半天才发现是列表默认值在背后“作妖”。还有人以为 print 就是返回,结果把函数当工具用在别处,拿到 None 一头雾水。多走几次这样的弯路,就会把这些细节刻到脑子里。
说点实用的写法,方便照着用:当你需要一个可选的容器参数,用 lst=None,然后在函数里写 if lst is None: lst = []。需要判断是不是没返回值就用 if result is None: 来处理。要返回多个东西,写 return a, b,这样调用方直接 a, b = func()。遇到参数多、调用时不想记顺序的场景,鼓励用关键字参数,调用更直观。遇到需要默认行为但又想保持线程安全或无副作用时,尽量在函数内部创建新对象而不是依赖外部的可变默认。
别把这些规则当成教条,更多是工具箱里的工具。实战中配合打印、断言和测试,许多莫名其妙的问题都能迅速定位。写代码的过程就是不断把这些小技巧变成习惯,少走弯路,多点儿“稳”。你要改写一个函数,把 print 换成 return,或把默认参数从 [] 改成 None,试一次就知道差别。