Python学习之函数式编程特性(map/filter/lambda)
Python 3 函数式编程特性(lambda / map / filter)
Python 不是纯函数式语言,但从一开始就吸收了函数式编程的核心思想——把函数当作一等公民,支持高阶函数、匿名函数、惰性求值等特性。这篇文档聚焦最常用的三个工具:lambda、map、filter,以及它们的替代写法和最佳实践。
一、lambda —— 匿名函数
1.1 基本语法
lambda 参数列表: 表达式
lambda 创建的是一个单行匿名函数,等价于用 def 定义一个只有 return 语句的函数:
# lambda 写法
square = lambda x: x ** 2
# 等价的 def 写法
def square(x):
return x ** 2
# 调用方式完全一样
square(5) # 25
1.2 多参数
add = lambda a, b: a + b
add(3, 7) # 10
greet = lambda name, time: f"Good {time}, {name}!"
greet("Alice", "morning") # 'Good morning, Alice!'
1.3 带默认参数
power = lambda base, exp=2: base ** exp
power(3) # 9(使用默认 exp=2)
power(3, 3) # 27
1.4 lambda 的典型使用场景
lambda 通常不会单独赋值给变量使用(那样不如直接写 def),它最大的价值是作为参数传入高阶函数,在需要一个临时小函数的地方"即用即弃":
# 排序时指定 key
students = [("Alice", 88), ("Bob", 95), ("Charlie", 82)]
students.sort(key=lambda s: s[1]) # 按分数排序
# [('Charlie', 82), ('Alice', 88), ('Bob', 95)]
# 按字符串的某个属性排序
words = ["banana", "pie", "Washington", "book"]
words.sort(key=lambda w: len(w)) # 按长度排序
# ['pie', 'book', 'banana', 'Washington']
words.sort(key=lambda w: w.lower()) # 不区分大小写排序
# ['banana', 'book', 'pie', 'Washington']
1.5 lambda 的限制
lambda 只能包含一个表达式,不能包含语句(赋值、if 块、for 循环等):
# 不能这样做:
# lambda x: if x > 0: return x else: return 0 # SyntaxError
# 可以用三元表达式替代:
safe_div = lambda a, b: a / b if b != 0 else float('inf')
safe_div(10, 2) # 5.0
safe_div(10, 0) # inf
二、map() —— 映射变换
2.1 基本语法
map(函数, 可迭代对象)
map 将函数依次应用到可迭代对象的每个元素上,返回一个惰性的 map 对象(迭代器),不会立即计算所有结果。
numbers = [1, 2, 3, 4, 5]
# 将每个元素平方
result = map(lambda x: x ** 2, numbers)
print(result) # <map object at 0x...>(惰性的,还没计算)
list(result) # [1, 4, 9, 16, 25](触发计算)
2.2 用具名函数更清晰
当函数逻辑稍复杂时,用 def 定义具名函数比塞 lambda 更易读:
numbers = [1, 2, 3, 4, 5]
def square(x):
return x ** 2
list(map(square, numbers)) # [1, 4, 9, 16, 25]
2.3 传入多个可迭代对象
当传入多个可迭代对象时,函数会同时接收每个对象的对应元素:
a = [1, 2, 3]
b = [10, 20, 30]
list(map(lambda x, y: x + y, a, b)) # [11, 22, 33]
# 实际应用:合并两个列表的数据
names = ["Alice", "Bob", "Charlie"]
scores = [88, 95, 82]
list(map(lambda n, s: f"{n}: {s}", names, scores))
# ['Alice: 88', 'Bob: 95', 'Charlie: 82']
当多个可迭代对象长度不同时,map 会在最短的那个用完时停止:
list(map(lambda x, y: x + y, [1, 2, 3], [10, 20]))
# [11, 22](只有两个结果)
2.4 map 配合内置函数
map 不一定需要 lambda,任何函数都可以传入:
# 将字符串列表转为整数
str_nums = ["10", "20", "30"]
list(map(int, str_nums)) # [10, 20, 30]
# 全部转大写
words = ["hello", "world"]
list(map(str.upper, words)) # ['HELLO', 'WORLD']
# 保留两位小数
values = [3.14159, 2.71828, 1.41421]
list(map(lambda v: round(v, 2), values)) # [3.14, 2.72, 1.41]
2.5 实际应用示例
# 从字典列表中提取某个字段
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
{"name": "Charlie", "age": 28},
]
names = list(map(lambda u: u["name"], users))
# ['Alice', 'Bob', 'Charlie']
# 批量处理文件路径
import os
paths = ["docs/report.pdf", "images/photo.jpg", "src/main.py"]
basenames = list(map(os.path.basename, paths))
# ['report.pdf', 'photo.jpg', 'main.py']
# 矩阵转置(配合 zip)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = list(map(list, zip(*matrix)))
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
三、filter() —— 条件过滤
3.1 基本语法
filter(函数, 可迭代对象)
filter 将函数依次应用到每个元素,保留函数返回值为 True 的元素,同样返回一个惰性的迭代器。
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 筛选偶数
evens = filter(lambda x: x % 2 == 0, numbers)
list(evens) # [2, 4, 6, 8, 10]
# 筛选大于 5 的数
bigger = filter(lambda x: x > 5, numbers)
list(bigger) # [6, 7, 8, 9, 10]
3.2 过滤函数的返回值规则
传入的函数不一定要返回 True / False,Python 会按布尔值来判定——任何"真值"都保留,"假值"都过滤掉:
# 过滤掉 None 值
items = [0, 1, None, "", "hello", [], [1, 2], False, True]
truthy = list(filter(None, items))
# [1, 'hello', [1, 2], True]
# 传入 None 作为函数时,filter 直接用元素本身的真值来判断
3.3 实际应用示例
# 过滤掉空字符串
lines = ["hello", "", "world", "", "python", "" ]
non_empty = list(filter(None, lines))
# ['hello', 'world', 'python']
# 筛选满足条件的对象
files = ["report.pdf", "data.csv", "image.png", "notes.txt", "backup.pdf"]
pdfs = list(filter(lambda f: f.endswith(".pdf"), files))
# ['report.pdf', 'backup.pdf']
# 从字典列表中筛选
products = [
{"name": "Widget", "price": 9.99, "in_stock": True},
{"name": "Gadget", "price": 24.99, "in_stock": False},
{"name": "Doohickey", "price": 4.99, "in_stock": True},
]
available = list(filter(lambda p: p["in_stock"], products))
# [{'name': 'Widget', ...}, {'name': 'Doohickey', ...}]
# 过滤掉异常值
sensor_data = [22.3, 999.9, 21.8, -1.0, 23.1, 999.9, 22.7]
valid = list(filter(lambda v: 0 <= v <= 100, sensor_data))
# [22.3, 21.8, 23.1, 22.7]
四、map + filter 组合使用
map 和 filter 可以链式组合,形成数据处理管道:
# 先过滤,再映射
numbers = range(1, 21)
# 找出偶数的平方
result = map(lambda x: x ** 2,
filter(lambda x: x % 2 == 0, numbers))
list(result) # [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
# 实际场景:从用户列表中筛选活跃用户,提取邮箱
users = [
{"email": "alice@test.com", "active": True},
{"email": "bob@test.com", "active": False},
{"email": "carol@test.com", "active": True},
{"email": "dave@test.com", "active": False},
]
active_emails = list(
map(lambda u: u["email"],
filter(lambda u: u["active"], users))
)
# ['alice@test.com', 'carol@test.com']
五、reduce() —— 累积归约
reduce 不像 map / filter 那样是内置函数,它住在 functools 模块里,但同样是函数式编程的重要工具。
from functools import reduce
# 基本语法
reduce(函数, 可迭代对象, 初始值(可选))
reduce 依次将函数应用到累积值和当前元素上,最终将整个序列归约为单个值:
from functools import reduce
# 求和
nums = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, nums)
# 15
# 求乘积
product = reduce(lambda acc, x: acc * x, nums)
# 120
# 找最大值
maximum = reduce(lambda a, b: a if a > b else b, nums)
# 5
reduce 的工作过程详解:
reduce(lambda acc, x: acc + x, [1, 2, 3, 4, 5])
步骤1: acc=1, x=2 → 1+2 = 3
步骤2: acc=3, x=3 → 3+3 = 6
步骤3: acc=6, x=4 → 6+4 = 10
步骤4: acc=10, x=5 → 10+5 = 15
结果: 15
带初始值:
# 初始值为 0
reduce(lambda acc, x: acc + x, [1, 2, 3], 0) # 6
# 初始值为 1(乘法单位元)
reduce(lambda acc, x: acc * x, [1, 2, 3, 4], 1) # 24
# 将列表拼成字典
pairs = [("a", 1), ("b", 2), ("c", 3)]
d = reduce(lambda acc, p: {**acc, p[0]: p[1]}, pairs, {})
# {'a': 1, 'b': 2, 'c': 3}
注意:Python 社区普遍认为
reduce的可读性不如显式的for循环。简单的求和、求最大值等操作,优先使用内置函数sum()、max()、min()。只在需要自定义归约逻辑时才考虑reduce。
六、列表推导式 vs map/filter —— 该用哪个
对同一个任务,列表推导式和 map / filter 通常都能实现。选择哪个取决于可读性:
| 场景 | 推荐写法 | 示例 |
|---|---|---|
| 简单映射 | 列表推导式 | [x ** 2 for x in nums] |
| 简单过滤 | 列表推导式 | [x for x in nums if x > 0] |
| 映射 + 过滤 | 列表推导式 | [x ** 2 for x in nums if x > 0] |
| 需要惰性求值 | map / filter |
处理大数据集时节省内存 |
| 已有现成函数 | map |
map(int, str_list) |
| 需要多可迭代对象并行 | map |
map(f, a, b, c) |
# 这两种写法等价,列表推导式更 Pythonic:
nums = [1, 2, 3, 4, 5]
# map/filter 写法
result = list(map(lambda x: x ** 2, filter(lambda x: x > 2, nums)))
# 列表推导式写法(推荐)
result = [x ** 2 for x in nums if x > 2]
核心建议: 在 Python 中,优先使用列表推导式(可读性更好)。在需要惰性求值处理大数据、或者已经有一个现成函数可以直接传入时,map / filter 才是更好的选择。
七、其他函数式工具
Python 的 functools 和 itertools 模块提供了更多函数式编程工具,值得了解:
functools.partial —— 固定部分参数
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
square(5) # 25
cube(3) # 27
itertools 常用工具
from itertools import chain, groupby, takewhile, dropwhile
# chain:拼接多个可迭代对象
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
# takewhile:从开头取元素,直到条件不满足
list(takewhile(lambda x: x < 5, [1, 3, 5, 2, 4])) # [1, 3]
# dropwhile:从开头跳过元素,直到条件不满足
list(dropwhile(lambda x: x < 5, [1, 3, 5, 2, 4])) # [5, 2, 4]
八、速查表
| 工具 | 作用 | 示例 |
|---|---|---|
lambda x: expr |
创建匿名函数 | lambda x, y: x + y |
map(f, iterable) |
对每个元素应用函数 | map(str.upper, words) |
filter(f, iterable) |
保留使函数返回真值的元素 | filter(lambda x: x > 0, nums) |
reduce(f, iterable) |
累积归约为单个值 | reduce(lambda a, b: a + b, nums) |
functools.partial |
固定函数的部分参数 | partial(pow, exp=2) |
itertools.chain |
拼接多个可迭代对象 | chain(a, b, c) |
本文来自博客园,作者:code0101,转载请注明原文链接:https://www.cnblogs.com/cmct/p/20042799
浙公网安备 33010602011771号