前言
函数在 Python 里有两种诞生方式:用def起名创建,或用lambda匿名创建。创建之后,它就是一个普通的对象——可以赋值、可以放进列表、可以当参数传给别的函数、也可以被函数返回。调用则更丰富:按位置调、按关键字调、用*/**把序列或字典展开后调。
很多人卡在「只会def和f()」这一步,一旦看到sorted(data, key=lambda kv: kv[1])或者回调式的写法就发懵。其实这些写法没有新语法,只是把「函数是对象」这一件事用透了。本文把「创建」和「调用」两条线分开讲清楚,再合起来给一个可运行的例子。
版本提醒:Python 2.7 已于 2020 年 1 月 1 日停止维护。Python 2 里print是语句(print x),lambda里塞不下它;Python 3 里print是函数(print(x)),而且因为函数调用属于表达式,Python 3 的lambda体里反而可以调用print。本文全按 Python 3 写。
一、用 def 创建函数
def创建的函数有名字、可以有多条语句、可以有文档字符串:
# 适用于 Python 3.8+
def average(numbers):
"""返回序列的平均值;空序列返回 None。"""
if not numbers:
return None
return sum(numbers) / len(numbers)
print(average([1, 2, 3, 4])) # 2.5
print(average([])) # None创建函数的动作在执行到def那一行时发生,函数体此时不执行。真正执行发生在调用时。
内层也可以再def,形成嵌套函数,用来做辅助或工厂:
# 适用于 Python 3.8+
def make_power(exp):
"""返回一个把输入取 exp 次幂的函数。"""
def power(x):
return x ** exp
return power
square = make_power(2)
cube = make_power(3)
print(square(5)) # 25
print(cube(3)) # 27make_power返回的就是「创建出来的函数对象」。
二、用 lambda 创建匿名函数
lambda用来创建没有名字的小函数,语法是lambda 参数列表: 表达式。它只能是一个表达式,不能写语句块:
# 适用于 Python 3.8+
add = lambda x, y: x + y
print(add(2, 3)) # 5
print((lambda x: x * 2)(10)) # 20 ← 定义后立即调用lambda的价值通常在于「作为参数内联传入」,不必为它专门起名字:
# 适用于 Python 3.8+
pairs = [("b", 2), ("a", 3), ("c", 1)]
by_second = sorted(pairs, key=lambda kv: kv[1])
print(by_second) # [('c', 1), ('b', 2), ('a', 3)]
by_first = sorted(pairs, key=lambda kv: kv[0])
print(by_first) # [('a', 3), ('b', 2), ('c', 1)]lambda的局限要清楚:不能有多个表达式、不能有赋值语句、不能有return(表达式的结果本身就是返回值)。复杂逻辑还是用def,给函数起个名字,可读性和可调试性都更好。
三、创建后的函数是对象
函数是对象,所以下面这些操作都合法:
# 适用于 Python 3.8+
def double(x):
return x * 2
f = double # ① 赋值给变量
print(f(4)) # 8
funcs = [double, abs] # ② 放进容器
print([g(-3) for g in funcs]) # [-6, 3]
def apply(func, value): # ③ 当参数传入
return func(value)
print(apply(double, 5)) # 10
print(apply(len, "hello")) # 5把函数当参数传、或当返回值返回的函数,叫高阶函数(higher-order function)。内置的map、filter、sorted的key都是这个用法。
四、调用的形式与时机
调用时决定「哪个实参给哪个形参」,有四种写法。
# 适用于 Python 3.8+
def describe(name, age, city="北京"):
return f"{name},{age} 岁,来自 {city}"
# ① 全位置
print(describe("小明", 18))
# ② 位置 + 关键字
print(describe("小明", 18, city="上海"))
# ③ 全关键字(顺序随便)
print(describe(age=18, name="小明", city="广州"))
# ④ 用 * 和 ** 解包
args = ("小红", 20)
kwargs = {"city": "深圳"}
print(describe(*args, **kwargs))规则只有一条要记牢:位置参数必须在关键字参数之前。describe(name="小明", 18)会直接SyntaxError。
调用时可用的参数数量必须和签名匹配:少了必需参数报TypeError: missing required positional argument,多了报TypeError: takes N positional arguments but M were given。
写f()就是立即调用;把f(不带括号)传出去,则在别人决定的时候调用,这叫延迟调用或回调(callback)。
# 适用于 Python 3.8+
def on_success(value):
print("成功:", value)
def on_failure(msg):
print("失败:", msg)
def run(ok, on_ok, on_err):
if ok:
on_ok(42) # 在这里才调用
else:
on_err("出错了")
run(True, on_success, on_failure) # 成功: 42
run(False, on_success, on_failure) # 失败: 出错了要区分func与func():前者是「把函数本身传出去」,后者是「立刻调用并传出结果」。这是回调类代码最常见的错。
实战:一个用函数对象做成的小工具
下面把创建、高阶函数、关键字调用、解包都串起来,可直接运行。
# 适用于 Python 3.8+
def build_operators():
"""创建一组运算函数并放进字典。"""
return {
"add": lambda a, b: a + b,
"sub": lambda a, b: a - b,
"mul": lambda a, b: a * b,
"max": lambda a, b: max(a, b),
}
def apply_all(operators, a, b):
"""对同一组输入逐个调用运算符函数,返回结果字典。"""
return {name: fn(a, b) for name, fn in operators.items()}
def repeat_call(func, times, *args, **kwargs):
"""把 func 用同样的参数调用 times 次,返回结果列表。"""
return [func(*args, **kwargs) for _ in range(times)]
if __name__ == "__main__":
ops = build_operators()
print(apply_all(ops, 6, 3))
# {'add': 9, 'sub': 3, 'mul': 18, 'max': 6}
print(ops["add"](10, 5)) # 15 ← 从字典取函数再调用
print(repeat_call(ops["mul"], 3, 2, 5)) # [10, 10, 10]要点:
build_operators里用lambda创建了四个匿名函数,装进字典,键是名字、值是函数对象。apply_all遍历字典的.items(),拿到每个函数并调用——这是「函数是对象」的直接应用。repeat_call用*args, **kwargs接收参数并原样透传,体现调用侧解包与定义侧收集的配合。
常见坑点
- 把
func和func()弄混
❌button.on_click(handler())—— 立刻调用了handler,把返回值当回调。 ✅button.on_click(handler)—— 传函数本身。
- 位置参数写在关键字参数后面
❌describe(name="小明", 18)——SyntaxError。 ✅describe("小明", age=18),位置参数永远在前。
lambda里想写语句
❌lambda x: print(x); x + 1——lambda体只能是单个表达式。 ✅ 复杂逻辑用def起名;只做映射/取键时才用lambda。
- 循环里创建 lambda 导致延迟绑定
❌fns = [lambda: i for i in range(3)]—— 全部返回最后一个i。 ✅fns = [lambda i=i: i for i in range(3)],用默认参数把当前值绑定。
- 调用时参数个数不匹配
❌ 少传必需参数或把函数当对象传给str拼接,报TypeError。 ✅ 对照签名传参;不确定时先看help(func)显示的签名。
*解包把字符串当成可变位置参数
❌describe(*"小明")—— 字符串被拆成一个个字符,参数错位。 ✅ 把要展开的东西放进元组或列表:describe(*["小明", 18])。
- 嵌套函数返回时忘了返回内层函数
❌def make(): def inner(): ...但结尾没写return inner—— 得到None。 ✅ 结尾写return inner(注意不带括号)。
- 用 Python 2 的写法
❌print x、把lambda当成能放语句的工具(Python 2 里print是语句,更写不进去)。 ✅print(x);把print当函数才能放进需要函数的场合,如list(map(print, items))。
总结
| 环节 | 写法 | 要点 |
|---|
| 创建(具名) | def name(...): | 多条语句、文档字符串 |
| 创建(匿名) | lambda 参数: 表达式 | 单个表达式,适合内联 |
| 创建(工厂) | 函数内def后return | 返回的是函数对象 |
| 调用(位置) | f(1, 2) | 顺序对应形参 |
| 调用(关键字) | f(a=1, b=2) | 必须在位置参数之后 |
| 调用(解包) | f(*args, **kwargs) | 展开序列 / 字典 |
「函数是对象,所以能被创建、被传递、被延迟调用」——这句话是理解 Python 函数的钥匙。把def与lambda两种创建方式、以及位置 / 关键字 / 解包三种调用方式记熟,再分清f与f()的区别,回调、高阶函数这些概念自然就通了。