第 2 章 · 函数¶
函数是 Python 的头等公民(first-class citizen)。在 Java 里,方法必须挂在类上、想当"值"传递得靠函数式接口/方法引用绕一圈;在 Python 里,函数就是普通对象——能赋值、能塞进容器、能当参数传、能当返回值。这是你要适应的第一个大差异。
2.1 定义函数:def¶
-
Java
-
Python
要点:
def定义,参数和返回值的类型注解是可选的(运行时不强制,给工具看)。- 函数没有显式
return时,自动返回None(Java 是编译错误或void)。 - 文档字符串(docstring):函数体第一行的字符串,是 Python 版的 Javadoc:
2.2 函数是一等公民¶
这是和 Java 最大的心智差异之一:
-
Java:函数不是"值"
-
Python:函数就是对象
函数可以被赋值、传递、返回:
Java 程序员的视角
Python 不需要 Function<T,R>、Consumer<T>、Supplier<T> 这一整套函数式接口——任何函数都能直接传递,签名靠类型注解表达(如 Callable[[int], int],第 8 章详讲)。
2.3 参数的四种姿势¶
Python 的参数远比 Java 灵活。掌握这四种,你就能读懂大部分 Python 代码。
① 默认参数¶
-
Java
-
Python
② 关键字参数(keyword arguments)¶
调用时可按 名字=值 传参,顺序无关——这在参数多时非常清晰:
Java 没有这个特性(要么靠 builder 模式模拟),这是 Python 的显著便利。
③ *args:可变位置参数(Java 的 int...)¶
-
Java
-
Python
*nums 把所有多余的位置参数收集成 tuple。
④ **kwargs:可变关键字参数¶
Java 完全没有对应物。它收集所有"名字=值"参数成一个 dict:
def create_user(name, **fields):
print(name, fields)
create_user("Alice", age=30, role="admin")
# Alice {'age': 30, 'role': 'admin'}
2.4 ⚠️ 可变默认参数陷阱(经典必踩)¶
这是 Python 最臭名昭著的坑,每个 Java 程序员都会踩一次:
致命陷阱
原因:默认参数在函数定义时只求值一次,所有调用共享同一个 list 对象。
为什么 Java 没这个问题:Java 没有默认参数,每次调用都新建容器。
正解:用 None 作哨兵,函数体里再创建:
def add_item(item, basket=None):
if basket is None:
basket = [] # 每次调用新建
basket.append(item)
return basket
Pythonic 写法
记住口诀:可变默认值(list/dict/set)一律用 None 哨兵替代。
2.5 多返回值 = tuple 解包¶
Java 想返回多个值要造一个类、用 Pair/Record 或传输出参数。Python 直接"返回多个"(本质是返回 tuple):
-
Java
-
Python
解包还能交换变量(不需要临时变量):
2.6 lambda:受限的匿名函数¶
-
Java lambda(可多语句)
-
Python lambda(只能单表达式)
⚠️ Java 程序员的陷阱
别把 Java 习惯带过来,到处用 lambda 写复杂逻辑。Python 的 lambda 只能是一个表达式,不能写语句块。需要复杂逻辑就老老实实 def 一个命名函数——Python 社区推崇"显式胜于隐式",命名函数远比一长串 lambda 清晰。
lambda 真正的舞台是作为简短的回调(sorted、map、filter 的 key):
2.7 闭包与变量捕获¶
Python 闭包捕获的是变量本身(可观察到后续变化),而 Java lambda 要求捕获变量是 effectively final。
def make_counters():
count = 0
def inc():
nonlocal count # 要修改外层变量,需声明 nonlocal
count += 1
return count
return inc
c = make_counters()
c() # 1
c() # 2
与 Java 的区别
- Java lambda 只能读外层变量,且变量必须 effectively final。
- Python 闭包能读写外层变量,写需要
nonlocal声明。
还有个"晚绑定"陷阱值得知道:
因为 lambda 捕获的是变量 i 本身,循环结束时 i == 2。修正:lambda i=i: i(用默认参数固化当时的值)。
本章练习¶
练习 2.1
下面函数有什么 bug?修复它。
参考答案
默认参数 tags=[] 是可变对象,会在多次调用间共享。改为:
练习 2.2
写一个函数 compose(f, g),返回一个新函数 h(x) = f(g(x))。体会"函数是一等公民"。
参考答案
练习 2.3
解释 funcs = [lambda: i for i in range(3)] 为何 [f() for f in funcs] 得到 [2, 2, 2],并给出两种修复方法。
参考答案
lambda 捕获变量 i 本身(引用),循环结束后 i 值为 2,故三个函数都返回 2。
修复一:默认参数固化 lambda i=i: i。修复二:用普通函数 + 闭包工厂,或用生成器/functools.partial。
下一章:第 3 章 · 数据模型与"一切皆对象"。