跳到主要内容

返回函数

Python 中的函数也是对象,可以像数字或字符串一样被保存、传递和返回。一个函数内部创建并返回另一个函数,就是“返回函数”。这种写法把“准备规则”和“执行规则”分开,是高阶函数、闭包和装饰器的基础。

本文使用 Python 3.10 及以上版本。读完后,你应该能够区分返回函数与立即调用函数,理解闭包如何保存外部变量,并知道何时用函数工厂封装配置。

返回函数与返回调用结果​

返回函数名时,返回的是函数对象;加上括号则会立即调用函数,返回调用结果:

def double(number):
return number * 2


def choose_operation():
return double


operation = choose_operation()
print(operation(5)) # 10

上例中的 choose_operation() 返回 double,所以 operation 仍然可以被调用。下面的写法则返回整数 10,调用者无法再把结果当成函数使用:

def call_double():
return double(5)


result = call_double()
print(result) # 10

判断一个值是否可调用,可以使用内置函数 callable():

print(callable(operation)) # True
print(callable(result)) # False

最简单的函数工厂​

如果一组函数只有某个配置不同,可以写一个函数工厂:工厂接收配置,返回一个已经准备好的函数。

def make_multiplier(factor):
def multiply(number):
return number * factor

return multiply


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(4)) # 8
print(triple(4)) # 12

调用 make_multiplier(2) 时,内部函数 multiply 被创建并返回。之后调用 double(4),才会把 4 乘以保存的 factor。

函数工厂适合把重复的配置集中到创建阶段,例如税率、日志前缀、权限范围或单位换算规则:

def make_price_converter(rate):
def convert(price):
return round(price * rate, 2)

return convert


to_usd = make_price_converter(0.14)
print(to_usd(100)) # 14.0

闭包:返回函数如何记住变量​

内部函数引用了外部函数的局部变量,并且外部函数已经返回时,这种函数和它引用的环境一起称为闭包。double 不仅是 multiply 的代码,还携带了创建它时的 factor=2。

可以通过函数的 __closure__ 观察被捕获的单元,但业务代码不应依赖这个实现细节:

double = make_multiplier(2)
print(double.__closure__[0].cell_contents) # 2

闭包捕获的是变量绑定,而不是创建时变量的一个普通副本。外部变量如果后来被重新绑定,闭包读取到的可能是新值。要构建一组闭包时,常见的晚绑定问题如下:

def make_operations():
operations = []
for factor in [1, 2, 3]:
operations.append(lambda number: number * factor)
return operations


operations = make_operations()
print([operation(10) for operation in operations]) # [30, 30, 30]

三个 lambda 共享同一个循环变量 factor,真正调用时它已经是 3。用默认参数在每轮固定当前值即可:

def make_operations():
operations = []
for factor in [1, 2, 3]:
operations.append(lambda number, factor=factor: number * factor)
return operations


print([operation(10) for operation in make_operations()]) # [10, 20, 30]

使用 nonlocal 修改闭包状态​

闭包默认只能读取外部变量。若要在内部函数中重新绑定外部函数的局部变量,需要使用 nonlocal:

def make_counter(start=0):
count = start

def next_count():
nonlocal count
count += 1
return count

return next_count


counter = make_counter(10)
print(counter()) # 11
print(counter()) # 12

nonlocal 只能找到外层函数作用域中的变量,不能修改全局变量;修改全局变量要使用 global,但通常应优先显式传递状态。闭包状态只适合简单、局部的场景;状态复杂时,类往往更容易阅读和测试:

class Counter:
def __init__(self, start=0):
self.count = start

def next(self):
self.count += 1
return self.count

类型提示返回的函数​

使用 collections.abc.Callable 可以说明函数工厂接收和返回的函数签名:

from collections.abc import Callable


def make_multiplier(factor: int) -> Callable[[int], int]:
def multiply(number: int) -> int:
return number * factor

return multiply

Callable[[int], int] 表示“接收一个整数并返回一个整数的可调用对象”。如果函数参数或返回值很多,可以为签名定义类型别名,避免重复书写。

常见错误与选择建议​

把函数调用结果当成函数​

def make_greeting(name):
return lambda: f"你好,{name}"


greeting = make_greeting("小林")
print(greeting()) # 你好,小林

greeting = make_greeting("小林") 是创建闭包;只有 greeting() 才是执行它。若写成 return lambda(): ...,语法上也是错误的,因为 lambda 的参数列表不能写调用括号。

返回过于复杂的匿名函数​

闭包适合短小、清晰的规则。逻辑包含多个分支、异常处理或需要独立测试时,应使用有名称的内部函数,甚至改用类。

忽略可变对象捕获​

闭包捕获列表或字典时,多个返回函数可能共享同一个可变对象。需要独立状态时,在工厂函数内部创建对象,而不是把外部对象直接捕获。

练习​

  1. 编写 make_greater_than(limit),返回判断数字是否大于 limit 的函数。
  2. 使用闭包实现一个带步长的计数器,并提供重置功能。
  3. 构造三个单位转换函数,修复循环中 lambda 的晚绑定问题。
  4. 比较闭包计数器和 Counter 类的可读性与测试方式。

小结​

  • 返回函数时传递函数对象,写括号则会立即执行函数。
  • 函数工厂可以把配置保存到闭包中,创建出行为不同的函数。
  • 闭包会记住外层变量;循环中创建闭包时要注意变量的晚绑定。
  • 使用 nonlocal 可以修改外层函数的局部变量,但复杂状态通常更适合用类表示。
  • Callable 类型提示能清楚表达函数工厂的输入和输出签名。