Skip to main content

函数进阶

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 与现代替代方案

mapfilter 是传统的高阶函数:

>>> 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 参数

sortedkey 参数是一个很实用的高阶函数场景:

>>> 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

这在需要维护状态的场景下很有用。上面的例子中,doubletriple 记住了各自的 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_multiplierfactor 变量,即使 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 用于在闭包中修改外部变量