1. 引言:为什么需要 map()
在 Python 中,我们经常需要对一个可迭代对象(列表、元组、字符串、生成器等)中的每一个元素 执行同样的操作,然后把结果收集起来。最直观的写法是 for 循环:
python
nums = [1, 2, 3, 4, 5]
squares = []
for n in nums:
squares.append(n * n)
print(squares) # [1, 4, 9, 16, 25]
这段代码没有问题,但略显啰嗦。Python 提供了 map() 这个内置函数,用一行就能表达同样的语义:
python
nums = [1, 2, 3, 4, 5]
squares = list(map(lambda n: n * n, nums))
print(squares) # [1, 4, 9, 16, 25]
map() 的核心思想是函数式编程中的映射(mapping):把一个函数"施加"到一个序列的每个元素上,得到一个新的序列。它把"遍历"这件事交给解释器,你只需要关心"对单个元素做什么"。
本文会从语法、返回值、惰性求值原理,一路讲到多参数用法、与列表推导式的取舍、常见坑和真实场景,力求把 map() 讲透。
2. 基本语法与参数说明
map() 是 Python 的内置函数(built-in),无需导入即可使用。它的签名如下:
python
map(function, iterable, *iterables)
参数说明:
| 参数 | 是否必填 | 说明 |
|---|---|---|
function |
必填 | 一个可调用对象(函数、lambda、内置函数、类、方法等),作用于每个元素 |
iterable |
必填 | 一个可迭代对象,提供要处理的元素 |
*iterables |
可选 | 额外的可迭代对象,可以有任意多个 |
返回值:一个 map 对象(迭代器),而不是列表。这一点非常关键,后文会专门展开。
最简单的例子:
python
result = map(str, [1, 2, 3])
print(result) # <map object at 0x...>
print(list(result)) # ['1', '2', '3']
注意 print(result) 打印出来的是 <map object at 0x...>,说明它并不是一个列表,而是一个惰性的迭代器对象。
3. 返回值:map 对象与惰性求值
3.1 map 对象是什么
map() 返回的是一个迭代器(iterator) ,在 Python 3 中它属于惰性求值(lazy evaluation):只有当你真正去迭代它时,函数才会被调用。
python
def trace(x):
print(f"处理 {x}")
return x * 2
m = map(trace, [1, 2, 3])
print("创建完成,尚未开始计算")
result = list(m)
print(result)
输出:
text
创建完成,尚未开始计算
处理 1
处理 2
处理 3
[2, 4, 6]
可以看到,map() 调用本身没有执行任何计算,直到 list(m) 触发迭代才开始逐个处理。
3.2 惰性求值的好处
- 节省内存:处理百万级数据时,不需要一次性把所有结果放进内存。
- 支持无限序列 :可以配合
itertools.count()等无限迭代器使用。 - 短路友好 :配合
next()可以只取前几个结果就停止。
python
from itertools import count
# 只取前 5 个平方数,不会无限执行
squares = map(lambda x: x * x, count(1))
print([next(squares) for _ in range(5)]) # [1, 4, 9, 16, 25]
3.3 map 对象是一次性的
迭代器只能被消费一次,消费完就"空了":
python
m = map(str, [1, 2, 3])
print(list(m)) # ['1', '2', '3']
print(list(m)) # [] ------ 已经耗尽
如果需要多次使用结果,请先转成 list 或 tuple 保存。
4. 常见用法示例
4.1 配合内置函数
map() 最常见的搭档是内置函数,代码简洁且性能好:
python
# 字符串转整数
strs = ["1", "2", "3"]
nums = list(map(int, strs))
print(nums) # [1, 2, 3]
# 求绝对值
print(list(map(abs, [-1, -2, 3]))) # [1, 2, 3]
# 转字符串
print(list(map(str, [1, 2, 3]))) # ['1', '2', '3']
# 求长度
print(list(map(len, ["a", "bb", "ccc"]))) # [1, 2, 3]
4.2 配合 lambda
当逻辑比较简单、不值得单独定义函数时,用 lambda 更紧凑:
python
nums = [1, 2, 3, 4]
print(list(map(lambda x: x ** 2, nums))) # [1, 4, 9, 16]
print(list(map(lambda x: x + 10, nums))) # [11, 12, 13, 14]
print(list(map(lambda s: s.strip(), [" a ", " b "]))) # ['a', 'b']
4.3 配合自定义函数
逻辑复杂时,定义具名函数可读性更好,也便于复用和测试:
python
def normalize(name: str) -> str:
return name.strip().title()
names = [" alice ", "BOB", " charlie"]
print(list(map(normalize, names))) # ['Alice', 'Bob', 'Charlie']
4.4 配合类构造器
map() 的第一个参数只要是可调用对象即可,类本身也是可调用的:
python
class Point:
def __init__(self, x):
self.x = x
def __repr__(self):
return f"Point({self.x})"
points = list(map(Point, [1, 2, 3]))
print(points) # [Point(1), Point(2), Point(3)]
4.5 配合方法
python
words = ["hello", "world"]
print(list(map(str.upper, words))) # ['HELLO', 'WORLD']
5. 多可迭代对象:并行映射
map() 支持传入多个可迭代对象,此时 function 需要接收同样数量的参数,元素会按位置一一对应地传入:
python
a = [1, 2, 3]
b = [10, 20, 30]
print(list(map(lambda x, y: x + y, a, b))) # [11, 22, 33]
5.1 长度不一致时的行为
当多个可迭代对象长度不同时,map() 会在最短的那个耗尽时停止,不会报错:
python
a = [1, 2, 3, 4]
b = [10, 20]
print(list(map(lambda x, y: x + y, a, b))) # [11, 22]
这一点与 zip() 的行为一致。如果你希望长度不一致时报错,需要自己先校验。
5.2 三个及以上可迭代对象
python
a = [1, 2, 3]
b = [10, 20, 30]
c = [100, 200, 300]
print(list(map(lambda x, y, z: x + y + z, a, b, c))) # [111, 222, 333]
5.3 与 zip 的关系
map(f, a, b) 在语义上等价于 map(lambda pair: f(*pair), zip(a, b)),但前者更直接、少一层解包:
python
a = [1, 2, 3]
b = [10, 20, 30]
print(list(map(lambda x, y: x * y, a, b))) # [10, 40, 90]
print(list(map(lambda p: p[0] * p[1], zip(a, b)))) # [10, 40, 90]
6. map() 与列表推导式、生成器表达式的对比
这是初学者最常纠结的问题:既然有列表推导式,为什么还要用 map()?
6.1 三种写法对照
python
nums = [1, 2, 3, 4]
# 列表推导式
r1 = [x * x for x in nums]
# map + lambda
r2 = list(map(lambda x: x * x, nums))
# 生成器表达式
r3 = list(x * x for x in nums)
print(r1, r2, r3) # [1, 4, 9, 16] [1, 4, 9, 16] [1, 4, 9, 16]
6.2 如何选择
| 场景 | 推荐写法 | 原因 |
|---|---|---|
已有现成函数(如 int、str、abs) |
map(func, it) |
无需 lambda,最简洁 |
| 需要条件过滤 | 列表推导式 | map 无法直接过滤 |
| 逻辑较复杂、需要多行 | 列表推导式 / 具名函数 | 可读性更好 |
| 需要惰性、大数据流 | map() 或生成器表达式 |
不占内存 |
| 多序列按位运算 | map(f, a, b) |
比 zip + 推导更直接 |
经验法则:当第一个参数是现成的具名函数时,map() 往往最优雅;一旦需要写 lambda 且逻辑不短,列表推导式通常更易读。
python
# 优雅:现成函数
list(map(int, ["1", "2", "3"]))
# 可读性更好:复杂逻辑用推导式
[x * 2 if x > 0 else 0 for x in nums]
7. 性能分析
7.1 map 与列表推导式的速度
对于简单操作,map() 配合内置函数通常比列表推导式略快,因为循环在 C 层完成;但配合 lambda 时,函数调用开销会抵消部分优势。
python
import timeit
setup = "nums = list(range(1000))"
t1 = timeit.timeit("list(map(str, nums))", setup=setup, number=10000)
t2 = timeit.timeit("[str(x) for x in nums]", setup=setup, number=10000)
t3 = timeit.timeit("list(map(lambda x: x * 2, nums))", setup=setup, number=10000)
t4 = timeit.timeit("[x * 2 for x in nums]", setup=setup, number=10000)
print(f"map + 内置函数: {t1:.4f}s")
print(f"列表推导式: {t2:.4f}s")
print(f"map + lambda: {t3:.4f}s")
print(f"推导式(运算): {t4:.4f}s")
具体数值因机器和 Python 版本而异,但一般规律是:
map(内置函数, ...)通常最快;map(lambda, ...)与列表推导式接近,甚至略慢;- 差异通常在毫秒级,除非在热点路径上,否则不必过度优化。
7.2 内存占用
map() 返回迭代器,内存占用是 O(1);列表推导式会一次性生成完整列表,内存占用是 O(n)。处理大文件或大数据流时,这个差异非常关键。
python
# 处理大文件:逐行读取并转换,内存友好
with open("big.txt", encoding="utf-8") as f:
lengths = map(len, f) # 不会一次性读入所有结果
total = sum(lengths)
8. 常见坑与注意事项
8.1 忘记转成 list
python
result = map(str, [1, 2, 3])
print(result) # <map object at 0x...>,不是想要的结果
map 对象不会自动展开,需要 list()、tuple()、set() 或直接迭代。
8.2 迭代器只能消费一次
python
m = map(int, ["1", "2"])
print(sum(m)) # 3
print(sum(m)) # 0 ------ 已耗尽
8.3 lambda 中的闭包陷阱
python
funcs = list(map(lambda x: lambda: x, [1, 2, 3]))
print([f() for f in funcs]) # [1, 2, 3],这里没问题
但如果 lambda 引用了循环变量,需注意延迟绑定问题,必要时用默认参数固定:
python
funcs = [lambda x=x: x for x in [1, 2, 3]]
print([f() for f in funcs]) # [1, 2, 3]
8.4 不要用 map 做副作用操作
map() 的设计目的是转换 ,不是执行副作用。下面这种写法虽然能跑,但不推荐:
python
# 不推荐:用 map 打印
list(map(print, [1, 2, 3]))
需要副作用时,直接用 for 循环更清晰。
8.5 异常会中断迭代
如果函数在处理某个元素时抛异常,迭代会立即中断:
python
try:
list(map(int, ["1", "x", "3"]))
except ValueError as e:
print("出错:", e) # 出错: invalid literal for int() with base 10: 'x'
需要容错时,可以在函数内部捕获,或改用带 try 的生成器。
9. 实战场景
9.1 批量类型转换
python
raw = ["1", "2", "3", "4"]
nums = list(map(int, raw))
print(sum(nums)) # 10
9.2 数据清洗
python
records = [" Alice ", "BOB ", " charlie"]
cleaned = list(map(lambda s: s.strip().lower(), records))
print(cleaned) # ['alice', 'bob', 'charlie']
9.3 字典字段提取
python
users = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
]
names = list(map(lambda u: u["name"], users))
print(names) # ['Alice', 'Bob']
9.4 与 filter 组合
python
nums = [1, 2, 3, 4, 5, 6]
# 先过滤偶数,再平方
result = list(map(lambda x: x * x, filter(lambda x: x % 2 == 0, nums)))
print(result) # [4, 16, 36]
9.5 并行处理多个列表
python
prices = [100, 200, 300]
quantities = [2, 1, 3]
totals = list(map(lambda p, q: p * q, prices, quantities))
print(totals) # [200, 200, 900]
10. 总结
map() 是 Python 中表达"逐元素转换"最直接的内置工具,核心要点回顾:
- 语法 :
map(function, iterable, *iterables),返回惰性迭代器。 - 惰性求值:不迭代就不计算,省内存、支持无限序列。
- 一次性 :迭代器消费完即空,需要复用先转
list。 - 多序列:按位并行,长度以最短为准。
- 取舍 :现成函数用
map最简洁;复杂逻辑或需要过滤时,列表推导式更易读。 - 性能 :
map+ 内置函数通常最快,但差异通常不大,优先考虑可读性。
掌握 map() 之后,你会发现很多"遍历 + 转换"的代码都能写得更短、更清晰。配合 filter()、zip()、itertools 等工具,可以写出非常地道的 Python 函数式风格代码。