前言「返回一个函数」和「返回一个值」在语法上只差一对括号结果却完全不同前者得到的是一个还没执行的行为后者得到的是已经算完的结果。初学时最常见的错误就是在工厂函数末尾写成return inner()——本想返回一个可复用的函数实际返回的是它第一次调用的结果后续再调用就报NoneType object is not callable或者干脆拿到一个固定值。第二种误解是认为必须用lambda才能返回函数。实际def定义的内层函数同样可以返回而且在需要多行逻辑、需要文档字符串、需要在调试栈里看清名字时def是唯一合理的选择。本文围绕「函数作为返回值」这一件事展开它解决了什么问题、返回值究竟该是函数对象还是调用结果、返回的函数靠什么保住状态、以及几种常见替代方案functools.partial、可调用对象、operator工厂分别在什么场景下更合适。示例以 Python 3 为基准functools.partial从 Python 3.0 起就在functools里nonlocal需要 3.0。Python 2.7 已于 2020 年 1 月 1 日停止维护本文不使用旧写法。一、返回值可以是任何对象函数也不例外Python 不区分「函数」和「普通对象」在返回位置上的待遇只要能被return出来就能被接住再调用。# 适用于 Python 3.0def make_power(exp):返回一个把底数提升到 exp 次方的函数。def power(base):return base ** expreturn power # 注意没有括号sq make_power(2)cube make_power(3)print(sq(5), cube(2)) # 25 8print(type(sq)) # class functionprint(callable(sq)) # True一次return只能返回一个对象但可以用元组一次性返回多个函数# 适用于 Python 3.0def make_pair_ops():def add(a, b):return a bdef sub(a, b):return a - breturn add, sub # 等价于返回一个元组add, sub make_pair_ops()print(add(3, 4), sub(3, 4)) # 7 -1二、返回函数 vs 返回调用结果这是本文最重要的一张对照表。假设inner是内层函数对象写法返回的对象的类型之后还能再调用吗真正的调用时机return inner函数对象能f()由接收方决定return inner()inner的返回值一般不能在return那一刻return lambda: inner()新的函数对象能f()由接收方决定return add, sub元组元素是函数能解包后调用由接收方决定# 适用于 Python 3.0def make_logger_bad(name):def log(msg):print(f[{name}] {msg})return log(初始化完成) # 立刻打印返回 Nonedef make_logger_good(name):def log(msg):print(f[{name}] {msg})return log # 返回函数bad make_logger_bad(A) # 导入/调用这一刻就打印了print(bad) # Nonegood make_logger_good(A)print(good) # function make_logger_good.locals.log at 0x...good(准备就绪) # [A] 准备就绪区别不只是「有没有括号」而是执行的时机被谁掌握。返回函数的价值就在这里把决定的权力交给调用方这也就是回调callback模式的基础。三、返回的函数靠什么记住状态返回出去的内层函数要能工作就必须把外层的变量一起带走——这就是闭包。# 适用于 Python 3.0def make_counter(start0):count startdef counter(step1):nonlocal countcount stepreturn countreturn counterc1 make_counter()c2 make_counter(100)print(c1(), c1()) # 1 2print(c2()) # 101print(c1()) # 3c1 和 c2 的状态互不影响每一次调用外层函数都会新建一个 cell所以返回的每个函数各有各的状态。反过来说返回函数不能靠模块级变量存状态那会让所有返回出来的函数共享同一份数据# 适用于 Python 3.0shared 0def make_bad_counter():def counter():global sharedshared 1return sharedreturn countera make_bad_counter()b make_bad_counter()print(a(), b()) # 1 2 —— 明明是两次“独立”创建却共享了计数要给返回的函数绑定常量除了闭包还有一招——默认参数。默认值在函数定义时求值天然起到快照作用# 适用于 Python 3.0def make_tagged(tag):return lambda msg, _tagtag: f[{_tag}] {msg}info make_tagged(INFO)print(info(done)) # [INFO] done四、几种替代方案返回函数的场景里下面几种写法有时更清楚方案写法适合闭包内层def引用外层变量逻辑多行、需要文档lambdalambda x: x 1单表达式、一次性functools.partialpartial(func, 绑定参数)只固定参数、不要新逻辑可调用对象定义__call__的类状态多、需要多个方法operator工厂itemgetter(1)取下标 / 属性 / 调方法functools.partial的签名是functools.partial(func, /, *args, **keywords)官方文档给出的近似等价实现是# 摘自官方文档适用于 Python 3.0def partial(func, /, *args, **keywords):def newfunc(*more_args, **more_keywords):return func(*args, *more_args, **{**keywords, **more_keywords})newfunc.func funcnewfunc.args argsnewfunc.keywords keywordsreturn newfunc从这段近似实现能读出三条关键行为位置参数是新调用追加到已有的args后面后来传的关键字会覆盖先前绑定的同名字段partial 对象上能直接查到func、args、keywords。# 适用于 Python 3.0from functools import partialdef power(base, exp):return base ** expsquare partial(power, exp2)print(square(5)) # 25print(square.func.__name__) # powerprint(square.keywords) # {exp: 2}# 位置绑定是“冻结前几个”追加参数只能往后排def greet(greeting, name):return f{greeting}, {name}say_hi partial(greet, Hi)print(say_hi(Alice)) # Hi, Alice实战构造器工厂与分派表# 适用于 Python 3.0def make_validator(min_len0, max_lenNone, allow_digitsTrue):返回一个校验字符串的函数。def validate(value):if not isinstance(value, str):return Falseif len(value) min_len:return Falseif max_len is not None and len(value) max_len:return Falseif not allow_digits and any(ch.isdigit() for ch in value):return Falsereturn Truevalidate.min_len min_len # 返回的函数上也能挂元数据return validateusername_rule make_validator(min_len3, max_len12)pure_rule make_validator(allow_digitsFalse)print(username_rule(alice)) # Trueprint(username_rule(ab)) # Falseprint(pure_rule(abc123)) # Falseprint(username_rule.min_len) # 3再给一个按名字取函数的工厂# 适用于 Python 3.0def make_parser(kind):def parse_int(text):return int(text)def parse_float(text):return float(text)def parse_csv(text):return [p.strip() for p in text.split(,)]return {int: parse_int, float: parse_float, csv: parse_csv}[kind]parse make_parser(csv)print(parse(a, b ,c)) # [a, b, c]需要强调的是这种「字典里现造函数」的写法每次调用都会新建所有内层函数对象虽然有轻微开销但换来的是清晰的封闭性。真正挑剔性能时应把字典提到模块级。常见坑点1. 多写一对括号返回了调用结果❌ 错误写法def make_multiplier(n):def multiply(x):return x * nreturn multiply() # TypeError: multiply() missing 1 required argument: x✅ 正确写法return multiply。只有确实想「立刻算一次」时才带括号。2. 循环里返回一批函数全都读到最后一个值❌ 错误写法def make_all():funcs []for i in range(3):funcs.append(lambda: i)return funcsprint([f() for f in make_all()]) # [2, 2, 2]✅ 正确写法默认参数绑定当前值或改用工厂函数def make_all():return [lambda ii: i for i in range(3)]print([f() for f in make_all()]) # [0, 1, 2]3. 用模块级变量当返回函数的状态❌ 错误写法state []def make_appender():def append(x):state.append(x)return statereturn append✅ 正确写法状态放在外层函数体内每次调用外层都拿到独立副本def make_appender():state []def append(x):state.append(x)return statereturn append4. 以为返回的 lambda 能被 pickle❌ 错误写法把返回的闭包或 lambda 存进某个要序列化的结构会抛AttributeError: Cant pickle local object。✅ 正确写法需要跨进程传递就用模块级具名函数或可调用类实例multiprocessing场景尤其要注意Process(target...)的目标必须可导入。5. 返回的函数忘记转交参数❌ 错误写法def make_adder(n):return lambda x: x ndef make_wrapper(func):def wrapper():return func() # 只接受零参数return wrapper✅ 正确写法用*args, **kwargs全量转交避免调用方传参时报TypeError。6. 用partial却想追加到已冻结的位置参数前面❌ 错误写法from functools import partialdef f(a, b):return a, bg partial(f, 1) # 冻结了第一个位置参数print(g(2)) # (1, 2)2 只能补到 b 上想固定后面的参数只能走关键字被部分应用的位置参数永远排在前面。✅ 正确写法g partial(f, b2)print(g(1)) # (1, 2)7. 返回函数时把self也返回出去导致对象无法回收❌ 错误写法class Service:def handler(self):def on_event():return self.data # 闭包持有了 selfreturn on_event✅ 正确写法只需要数据就捕获数据不要捕获整个selfclass Service:def handler(self):data self.datareturn lambda: data8. 用返回函数做「只初始化一次」却没考虑并发❌ 错误写法靠None哨兵 nonlocal做惰性初始化多线程下可能重复初始化。✅ 正确写法用functools.lru_cache(maxsizeNone)3.2或functools.cache3.9这类已处理并发的缓存装饰器# 适用于 Python 3.9from functools import cachecachedef load_config():return {debug: False}总结关注点正确做法返回什么要复用行为就return inner不带括号状态怎么存外层函数局部变量 nonlocal或默认参数快照循环里返回一堆函数默认参数ii或工厂函数逐层创建只固定参数用functools.partial位置参数只能冻结前几个状态多、方法多改用带__call__的类要跨进程 / 序列化用模块级具名函数或类别用闭包「返回函数」是把「什么时候执行」这个决定权交出去的手段用途集中在回调、工厂、装饰器和柯里化上。写的时候只需盯住两点返回的是函数对象而不是它的调用结果被返回的函数所依赖的状态必须封装在外层函数的局部作用域里不能落到模块级变量上。