函数进阶
Python 中的函数不仅仅是"执行一段代码"的工具,它们还是一等对象(first-class object)。这意味着函数可以像整数、字符串一样被赋值给变量、存入数据结构、作为参数传递、甚至作为返回值。理解这一点,能帮你写出更灵活、更简洁的代码。
函数是一等对象
在 Python 中,定义一个函数就是创建一个对象:
>>> def greet(name):
... """向某人问好"""
... return f"你好,{name}!"
...
>>> greet("AITC")
'你好,AITC!'
>>> greet.__doc__
'向某人问好'
>>> type(greet)
<class 'function'>
函数对象可以赋值给变量,通过变量名调用:
>>> say_hi = greet
>>> say_hi("Bob")
'你好,Bob!'
也可以把函数放进列表或字典里:
>>> funcs = [greet, str.upper, len]
>>> for f in funcs:
... print(f("hello"))
你好,hello!
HELLO
5
高阶函数
高阶函数(higher-order function)是指接受函数作为参数,或者返回函数作为结果的函数。
map、filter 与现代替代方案
map 和 filter 是传统的高阶函数:
>>> names = ["alice", "bob", "carol"]
>>> list(map(str.title, names))
['Alice', 'Bob', 'Carol']
>>> list(filter(lambda x: len(x) > 3, names))
['alice', 'carol']
但在现代 Python 中,列表推导式通常更易读:
>>> [n.title() for n in names]
['Alice', 'Bob', 'Carol']
>>> [n for n in names if len(n) > 3]
['alice', 'carol']
sorted 的 key 参数
sorted 的 key 参数是一个很实用的高阶函数场景:
>>> members = [("Alice", 90), ("Bob", 85), ("Carol", 92)]
>>> sorted(members, key=lambda x: x[1], reverse=True)
[('Carol', 92), ('Alice', 90), ('Bob', 85)]
key 接收一个函数,sorted 会根据这个函数的返回值来排序,而不是直接比较元素本身。
lambda 表达式
lambda 用于创建匿名函数,适合写简单的、一次性的函数:
>>> add = lambda a, b: a + b
>>> add(2, 3)
5
但 lambda 的功能有限:只能写单个表达式,不能包含语句(如赋值、循环)。如果逻辑复杂,还是定义一个普通函数更清楚:
# 不好的写法:lambda 太长,难读
sorted(data, key=lambda x: x['score'] * 0.7 + x['attendance'] * 0.3)
# 好的写法:拆成命名函数
def total_score(item):
return item['score'] * 0.7 + item['attendance'] * 0.3
sorted(data, key=total_score)
可调用对象
除了函数,任何实现了 __call__ 方法的类的实例都可以像函数一样被调用:
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
这在需要维护状态的场景下很有用。上面的例子中,double 和 triple 记住了各自的 factor。
可以用 callable() 判断一个对象是否可调用:
>>> callable(double)
True
>>> callable(42)
False
函数内省
函数对象有很多属性,可以通过内省来了解它们:
def example(a, b=10, *args, c, d=20, **kwargs):
pass
print(example.__name__) # example
print(example.__defaults__) # (10,)
print(example.__kwdefaults__) # {'d': 20}
更强大的工具是 inspect 模块:
import inspect
sig = inspect.signature(example)
for name, param in sig.parameters.items():
print(f"{name}: kind={param.kind.name}, default={param.default}")
输出:
a: kind=POSITIONAL_OR_KEYWORD, default=<class 'inspect._empty'>
b: kind=POSITIONAL_OR_KEYWORD, default=10
c: kind=KEYWORD_ONLY, default=<class 'inspect._empty'>
d: kind=KEYWORD_ONLY, default=20
仅限关键字参数
Python 3 引入了仅限关键字参数(keyword-only argument),强制调用者必须通过参数名传参:
def create_member(name, *, role="未分配", active=True):
return {"name": name, "role": role, "active": active}
# 正确
create_member("Alice", role="算法")
# 错误:第二个位置参数会被拒绝
create_member("Alice", "算法") # TypeError
* 后面的参数必须通过关键字传入。这在参数很多、容易混淆时非常有用。
函数注解
Python 3 支持给函数的参数和返回值添加注解(annotation):
def add(x: int, y: int) -> int:
return x + y
注解不会影响运行时的行为,它们只是元数据:
>>> add.__annotations__
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
类型检查工具(如 mypy)可以利用这些注解来静态检查代码中的类型错误。注解不是强制性的,Python 仍然是动态类型语言。
装饰器
装饰器(decorator)是一种高阶函数,它接收一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原函数代码的前提下,给函数添加额外功能。
基本用法
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时 {time.time() - start:.4f} 秒")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.5)
return "done"
slow_function()
# slow_function 耗时 0.5012 秒
@timer 等价于 slow_function = timer(slow_function)。
保留元信息
上面的 wrapper 函数会丢失原函数的 __name__ 和 __doc__。用 functools.wraps 可以保留:
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print(f"{func.__name__} 耗时 {time.time() - start:.4f} 秒")
return result
return wrapper
带参数的装饰器
装饰器本身也可以接收参数:
def retry(max_attempts=3):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第 {attempt} 次失败: {e}")
if attempt == max_attempts:
raise
return wrapper
return decorator
@retry(max_attempts=3)
def fetch_data():
# 可能失败的网络请求
pass
@retry(max_attempts=3) 先调用 retry(3),返回一个装饰器,再应用到 fetch_data。
functools.lru_cache
functools.lru_cache 是标准库提供的一个实用装饰器,用于缓存函数的结果,避免重复计算:
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # 瞬间出结果,否则递归会慢到无法完成
lru_cache 用字典缓存 (n) → fibonacci(n) 的映射。maxsize 控制缓存大小,最久未使用的会被淘汰(LRU = Least Recently Used)。
functools.singledispatch
singledispatch 可以根据参数的类型自动分发到不同的函数实现:
from functools import singledispatch
@singledispatch
def process(data):
raise NotImplementedError("不支持该类型")
@process.register(int)
def _(data):
return f"整数: {data}"
@process.register(str)
def _(data):
return f"字符串: {data}"
print(process(42)) # 整数: 42
print(process("hi")) # 字符串: hi
这类似于其他语言中的函数重载,但更加灵活。
闭包
闭包(closure)是指延伸了作用域的函数,它包含了函数定义体中引用、但不在定义体中定义的非全局变量。
def make_multiplier(factor):
def multiply(number):
return number * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
multiply 函数记住了外部函数 make_multiplier 的 factor 变量,即使 make_multiplier 已经执行完毕。
闭包 vs 类
闭包和类都可以用来维护状态:
# 用类
class Averager:
def __init__(self):
self.values = []
def __call__(self, value):
self.values.append(value)
return sum(self.values) / len(self.values)
# 用闭包
def make_averager():
values = []
def averager(value):
values.append(value)
return sum(values) / len(values)
return averager
闭包的代码更简洁,但类的可读性更好。选择哪种方式取决于场景和个人偏好。
nonlocal
当闭包需要修改外部函数的变量时,要用 nonlocal 声明:
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
c = make_counter()
print(c()) # 1
print(c()) # 2
print(c()) # 3
如果不加 nonlocal,Python 会认为 count 是局部变量,赋值时会报错。
小结
- 函数是一等对象,可以赋值、传递、返回
- 高阶函数让代码更抽象、更复用
- 列表推导式通常比
map/filter更易读 - 装饰器在不修改原函数的情况下添加功能
functools.wraps保留原函数的元信息lru_cache缓存耗时的函数调用- 闭包让函数"记住"外部状态
nonlocal用于在闭包中修改外部变量