函数式编程
Python 并非纯函数式语言,但提供了丰富的函数式工具。函数式编程的核心思想是用函数处理数据流,强调不可变数据和函数组合,与命令式的"先创建空列表再逐个 append"风格形成对比。
map、filter 与 reduce
map(function, iterable) 对可迭代对象的每个元素应用函数,返回迭代器而非列表,具有惰性求值特性,适合处理大数据流:
salaries = [18888.0, 232000.0, 8888.0, 15000.0]
# 给所有人加 1000 奖金
new_salaries = map(lambda s: s + 1000.0, salaries)
print(new_salaries) # <map object ...> —— 迭代器
print(list(new_salaries)) # [19888.0, 233000.0, 9888.0, 16000.0]
# 多序列 map:按最短序列停止
names = ["航仔", "翼王", "图妹"]
for item in map(lambda n, s: f"{n}: {s}", names, salaries):
print(item)
# 航仔: 18888.0
# 翼王: 232000.0
# 图妹: 8888.0
filter(function, iterable) 保留使函数返回真值的元素,同样返回迭代器。当 function 为 None 时,保留所有真值元素:
# 筛选工资超过 10000 的员工
high_earners = filter(lambda s: s > 10000, salaries)
print(list(high_earners)) # [18888.0, 232000.0, 15000.0]
# 过滤掉空字符串和 None
mixed = ["航仔", "", "翼王", None, "图妹", 0]
print(list(filter(None, mixed))) # ['航仔', '翼王', '图妹']
functools.reduce(function, iterable[, initializer]) 对序列做累积计算,function 接收两个参数。求和、求积、找最值等场景都可以用它表达:
from functools import reduce
# 求工资总和
total = reduce(lambda acc, s: acc + s, salaries)
print(total) # 274776.0
# 带初始值:从 10000 开始累加
total = reduce(lambda acc, s: acc + s, salaries, 10000.0)
print(total) # 284776.0
# 找最高工资的员工(配合 zip)
emp_pairs = zip(names, salaries[:3])
richest = reduce(lambda a, b: a if a[1] > b[1] else b, emp_pairs)
print(richest) # ('翼王', 232000.0)
reduce 在 Python 3 中被移到了 functools 模块,因为它虽然强大,但可读性往往不如显式循环。初学者应优先保证代码可读,在数据流转换场景下再考虑使用。
lambda 的高阶使用与限制
lambda 创建匿名函数,语法上只能包含单个表达式,不能写语句(如赋值、循环、try/except)。它适合作为临时回调传递,但复杂逻辑应使用 def:
# lambda 作为排序 key
employees = [
{"name": "航仔", "salary": 18888},
{"name": "翼王", "salary": 232000},
{"name": "图妹", "salary": 8888},
]
by_salary = sorted(employees, key=lambda e: e["salary"], reverse=True)
# lambda 闭包:工厂函数
def make_multiplier(n):
return lambda x: x * n
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5)) # 10 15
lambda 的常见陷阱是循环变量绑定问题。在循环中定义的 lambda 会共享同一个变量引用:
# ❌ 错误:所有函数都返回 4
funcs = [lambda: i for i in range(4)]
print([f() for f in funcs]) # [3, 3, 3, 3]
# ✅ 正确:通过默认参数绑定当前值
funcs = [lambda i=i: i for i in range(4)]
print([f() for f in funcs]) # [0, 1, 2, 3]
functools.partial 偏函数
partial(func, *args, **kwargs) 固定函数的部分参数,返回一个参数更少的新函数。它与 lambda 的区别在于保留了原函数的元信息(__name__、__doc__ 等),且对位置参数的绑定更直观:
from functools import partial
def calc_tax(salary, rate, deduction):
"""计算个税"""
return salary * rate - deduction
# 固定广州地区税率
guangzhou_tax = partial(calc_tax, rate=0.10, deduction=5000)
print(guangzhou_tax(18888.0)) # -3111.2(低于起征点)
print(guangzhou_tax(232000.0)) # 18200.0
# 固定位置参数:创建 int 转换器(base=2)
bin_to_int = partial(int, base=2)
print(bin_to_int("1010")) # 10
# 对比 lambda:partial 保留原函数信息
print(guangzhou_tax.func) # <function calc_tax ...>
print(guangzhou_tax.keywords) # {'rate': 0.1, 'deduction': 5000}
partial 特别适合为回调函数统一上下文(如事件处理中固定某些参数),或在函数式数据流中减少重复代码。
operator 模块
operator 模块提供了与 Python 内置运算符对应的函数,避免为简单操作写 lambda,提升可读性和性能:
import operator
# 算术操作
print(operator.add(3, 4)) # 7
print(operator.mul(3, 4)) # 12
# 获取元素/属性 —— 替代 lambda
data = [("航仔", 18888), ("翼王", 232000), ("图妹", 8888)]
# 按第二个元素(工资)排序
by_salary = sorted(data, key=operator.itemgetter(1))
# 按字典键取值
rows = [
{"name": "航仔", "age": 28},
{"name": "翼王", "age": 35},
]
by_age = sorted(rows, key=operator.itemgetter("age"))
# 获取对象属性
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
staff = [Employee("航仔", 18888), Employee("翼王", 232000)]
richest = max(staff, key=operator.attrgetter("salary"))
print(richest.name) # 翼王
# 调用对象方法
methodcaller("upper")("hello") # 'HELLO'
itemgetter 和 attrgetter 支持多字段提取,返回元组,可用于多级排序:
# 先按部门排序,再按工资排序
department_staff = [
("技术部", "航仔", 18888),
("技术部", "翼王", 232000),
("产品部", "图妹", 8888),
]
sorted_staff = sorted(department_staff, key=operator.itemgetter(0, 2))
列表推导式与函数式风格对比
Python 中列表推导式 [expr for x in iterable if cond] 在多数场景下比 map/filter 更易读,且性能通常更好(CPython 对推导式有优化)。但函数式风格在链式操作和复用已有函数时更优雅:
nums = [1, 2, 3, 4, 5, 6]
# 列表推导式:筛选偶数再平方
result = [x ** 2 for x in nums if x % 2 == 0]
print(result) # [4, 16, 36]
# 函数式风格:用已有函数组合
from functools import reduce
result = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, nums)))
print(result) # [4, 16, 36]
# 更优雅的函数组合:借助 operator
from operator import mul
result = list(map(lambda x: mul(x, x), filter(lambda x: x % 2 == 0, nums)))
当处理生成器管道时,函数式风格的优势更明显。多个 map/filter 可以惰性串联,不创建中间列表,内存占用极低:
import itertools
# 处理百万级数据:全程迭代器,不占用大量内存
lines = open("salary_data.txt") # 假设每行是 "name,salary"
parsed = map(lambda line: line.strip().split(","), lines)
valid = filter(lambda parts: len(parts) == 2, parsed)
salaries = map(lambda parts: float(parts[1]), valid)
high = filter(lambda s: s > 10000, salaries)
total = sum(itertools.islice(high, 1000)) # 只取前1000条求和
函数组合思想
虽然 Python 没有内置的函数组合运算符,但可以手动实现,把多个小函数串联成数据处理流水线:
def compose(*functions):
"""从右到左组合函数:compose(f, g, h)(x) == f(g(h(x)))"""
def inner(arg):
result = arg
for f in reversed(functions):
result = f(result)
return result
return inner
strip = lambda s: s.strip()
upper = lambda s: s.upper()
add_prefix = lambda s: f"[LOG] {s}"
process = compose(add_prefix, upper, strip)
print(process(" hello world ")) # [LOG] HELLO WORLD
这种思想在数据处理、ETL 流程和日志格式化中非常实用:把复杂操作拆成可测试、可复用的小函数,再用组合串联起来。