第 6 章 函数
6.1 函数定义与调用
函数是组织代码的基本单元,用 def 关键字定义:
def greet(name):
"""向指定的人打招呼"""
return f"Hello, {name}!"
result = greet("Alice")
print(result) # Hello, Alice!
函数也是对象,可以赋值给变量、作为参数传递:
say_hello = greet # 函数赋值给变量
print(say_hello("Bob")) # Hello, Bob!
print(type(greet)) # <class 'function'>
pass 语句
定义空函数时用 pass 占位:
def not_implemented_yet():
pass
# 或使用 ... (Ellipsis)
def also_not_implemented():
...
6.2 参数类型全解析
6.2.1 位置参数与关键字参数
def power(base, exponent):
return base ** exponent
# 位置参数 — 按顺序传递
power(2, 10) # 1024
# 关键字参数 — 按名字传递,顺序无所谓
power(exponent=10, base=2) # 1024
# 混合使用 — 位置参数必须在前
power(2, exponent=10) # 1024
# power(base=2, 10) # SyntaxError!
6.2.2 默认参数
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Alice", "Hi") # "Hi, Alice!"
可变默认参数陷阱:
# 错误示范!
def append_to(item, target=[]):
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← 不是 [2]!默认列表在所有调用间共享
# 正确做法:用 None 作为哨兵值
def append_to(item, target=None):
if target is None:
target = []
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [2] ✓
原因:默认参数在函数定义时求值一次,而非每次调用时。
6.2.3 可变参数 *args 与 **kwargs
# *args — 接收任意数量的位置参数,打包为元组
def total(*numbers):
return sum(numbers)
total(1, 2, 3) # 6
total(1, 2, 3, 4, 5) # 15
# **kwargs — 接收任意数量的关键字参数,打包为字典
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, city="Beijing")
# 组合使用
def func(a, b, *args, **kwargs):
print(f"a={a}, b={b}")
print(f"args={args}")
print(f"kwargs={kwargs}")
func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2
# args=(3, 4)
# kwargs={'x': 5, 'y': 6}
解包传参:
def add(a, b, c):
return a + b + c
args = [1, 2, 3]
add(*args) # 6 — 列表解包
kwargs = {"a": 1, "b": 2, "c": 3}
add(**kwargs) # 6 — 字典解包
# 同时解包
add(*[1, 2], **{"c": 3}) # 6
6.2.4 仅限位置参数 / 与仅限关键字参数 *
Python 3.8+ 引入 /,用于强制某些参数只能按位置传递:
# 仅限位置 普通 仅限关键字
def func(pos_only, /, normal, *, kw_only):
print(pos_only, normal, kw_only)
func(1, 2, kw_only=3) # ✓
func(1, normal=2, kw_only=3) # ✓
# func(pos_only=1, 2, kw_only=3) # TypeError! pos_only 不能按名字传
# func(1, 2, 3) # TypeError! kw_only 必须按名字传
为什么需要仅限位置参数?
# 参数名不重要时,避免调用者依赖参数名
def calculate(x, y, /, *, method="add"):
if method == "add":
return x + y
return x * y
# x、y 只是占位名,将来可以改名而不影响调用者
完整的参数顺序
def func(pos_only, /, normal, *args, kw_only, **kwargs):
pass
# 顺序:仅限位置 → / → 普通 → *args → 仅限关键字 → **kwargs
6.3 返回值与多值返回
# 没有 return 语句(或 return 后无表达式),返回 None
def do_nothing():
pass
result = do_nothing()
print(result) # None
# 多值返回(实际上是返回元组)
def min_max(data):
return min(data), max(data)
result = min_max([3, 1, 4, 1, 5])
print(result) # (1, 5) — 元组
print(type(result)) # <class 'tuple'>
lo, hi = min_max([3, 1, 4, 1, 5]) # 解包
6.4 作用域与 LEGB 规则
Python 按 LEGB 顺序查找变量:
- L — Local:函数内部
- E — Enclosing:外层函数(闭包)
- G — Global:模块级别
- B — Built-in:内置名称
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local — L
inner()
print(x) # enclosing — E
outer()
print(x) # global — G
global 和 nonlocal
count = 0
def increment():
global count # 声明使用全局变量
count += 1
increment()
print(count) # 1
def outer():
x = 10
def inner():
nonlocal x # 声明使用外层函数的变量
x += 1
inner()
print(x) # 11
outer()
注意:尽量避免使用 global,它会让代码难以维护和测试。
6.5 闭包(Closure)
当内层函数引用了外层函数的变量,并且外层函数返回了内层函数,就形成了闭包:
def make_multiplier(factor):
def multiply(x):
return x * factor # factor 是自由变量,被闭包"捕获"
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
# 查看闭包捕获的变量
print(double.__closure__[0].cell_contents) # 2
闭包的经典陷阱:
# 循环中创建闭包
functions = []
for i in range(5):
functions.append(lambda: i)
# 你以为输出 0, 1, 2, 3, 4?
for f in functions:
print(f(), end=" ") # 4 4 4 4 4 — 全是 4!
# 原因:lambda 捕获的是变量 i 的引用,不是值
# 循环结束后 i = 4
# 解决方案 1:默认参数(在定义时绑定值)
functions = []
for i in range(5):
functions.append(lambda x=i: x)
# 解决方案 2:额外的函数层
def make_func(i):
return lambda: i
functions = [make_func(i) for i in range(5)]
6.6 lambda 匿名函数
lambda 创建简单的一行函数:
# lambda 参数: 表达式
square = lambda x: x ** 2
add = lambda a, b: a + b
print(square(5)) # 25
print(add(3, 4)) # 7
主要用途:作为简短的回调函数:
# 排序
students = [("Alice", 90), ("Bob", 75), ("Charlie", 88)]
students.sort(key=lambda s: s[1], reverse=True)
# 过滤
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
# 映射
squares = list(map(lambda x: x**2, numbers))
限制:lambda 只能包含一个表达式,不能包含语句(if/for/while/赋值等)。复杂逻辑应使用 def。
6.7 高阶函数:map()、filter()、reduce()
高阶函数是接收函数作为参数,或返回函数的函数。
map() — 对每个元素应用函数
numbers = [1, 2, 3, 4, 5]
# map 返回迭代器
squares = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16, 25]
# 多参数 map
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b)) # [11, 22, 33]
# 常用:类型转换
str_nums = ["1", "2", "3"]
int_nums = list(map(int, str_nums)) # [1, 2, 3]
# 推导式替代(通常更推荐)
squares = [x**2 for x in numbers]
filter() — 过滤元素
numbers = range(1, 21)
# 过滤出偶数
evens = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# None 作为函数 — 过滤掉 falsy 值
data = [0, 1, "", "hello", None, True, False, [], [1]]
truthy = list(filter(None, data))
# [1, 'hello', True, [1]]
# 推导式替代
evens = [x for x in numbers if x % 2 == 0]
reduce() — 累积计算
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# 累积求和
total = reduce(lambda acc, x: acc + x, numbers) # 15
# 过程: ((((1+2)+3)+4)+5)
# 带初始值
total = reduce(lambda acc, x: acc + x, numbers, 100) # 115
# 累积求积
product = reduce(lambda acc, x: acc * x, numbers) # 120
# 实用场景:展平嵌套列表
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda acc, x: acc + x, nested) # [1, 2, 3, 4, 5, 6]
什么时候用推导式 vs map/filter?
推荐推导式——更 Pythonic、更直观。map/filter 在函数已存在时更简洁:
# 函数已存在时,map 更简洁
names = [" alice ", " BOB ", " Charlie "]
cleaned = list(map(str.strip, names)) # 比推导式更直接
cleaned = [name.strip() for name in names] # 也可以
6.8 递归与尾递归优化
# 阶乘
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
# 斐波那契(朴素递归,指数时间复杂度)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
# 加缓存 — 记忆化
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # 354224848179261915075 — 瞬间完成
Python 的递归限制
import sys
print(sys.getrecursionlimit()) # 1000(默认)
# 可以修改,但不推荐设太大
sys.setrecursionlimit(5000)
Python 不支持尾递归优化(这是设计决策,Guido 认为保留调用栈信息比优化更重要)。深度递归应改为迭代:
# 迭代版阶乘
def factorial_iter(n):
result = 1
for i in range(2, n + 1):
result *= i
return result
# 迭代版斐波那契
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
6.9 函数注解与类型提示(Type Hints)
类型提示让代码更清晰、更易维护,IDE 也能提供更好的补全和检查:
def greet(name: str) -> str:
return f"Hello, {name}!"
def add(a: int, b: int) -> int:
return a + b
# 类型提示不会强制执行——Python 仍然是动态类型
add("hello", " world") # 运行时不会报错
常用类型注解
from typing import Optional, Union
# 基本类型
def process(name: str, age: int, score: float, active: bool) -> None:
pass
# Optional — 可以是 None
def find_user(user_id: int) -> Optional[str]:
# 等价于 str | None(Python 3.10+)
return None
# Union — 多种类型
def parse(value: Union[str, int]) -> str:
# 等价于 str | int(Python 3.10+)
return str(value)
# 容器类型(Python 3.9+ 可直接用内置类型)
def process_items(items: list[str], lookup: dict[str, int]) -> set[int]:
return {lookup[item] for item in items}
# 默认参数 + 类型提示
def connect(host: str, port: int = 8080) -> bool:
return True
类型提示的真正价值在于配合 mypy 或 pyright 等静态检查工具使用(详见第 18 章)。
本章小结:函数是 Python 编程的基石。理解各种参数类型(特别是
*args、**kwargs、/、*)、闭包的变量捕获机制、以及可变默认参数陷阱,是写出健壮 Python 代码的关键。类型提示虽非强制,但现代 Python 项目中已成为标配。