Lambda 函数和推导式

这三个特性有一个共同点:它们让你用一个可读的表达式来表达那些原本需要多行代码的思想。使用得当时,它们会使代码更短更清晰。使用不当时,会让代码无法阅读。本章涵盖了何时使用每一个工具以及何时停止。
Lambda 函数
Lambda 是一个无名的、单表达式函数。你用 lambda 关键字创建它。它真正的用处在于你可以在需要的地方直接内联编写它,无需预先定义一个命名函数。这就是它与 sorted() 一起有用的原因。
double = lambda x: x * 2
double(5) # 10这等价于:
def double(x):
return x * 2在大多数情况下,使用 def。Lambda 有一个真正的优点:你可以在需要的地方直接内联编写它们,而无需命名它们。这就是它们与 sorted()、map() 和 filter() 一起有用的原因:
players = [("张三", 87), ("李四", 74), ("王五", 92)]
sorted(players, key=lambda p: p[1]) # 按得分排序(升序)
sorted(players, key=lambda p: p[1], reverse=True) # 按得分排序(降序)没有 lambda,你必须仅为 key= 参数定义一个命名函数。Lambda 保持意图局部化且可见。
Lambda 可以接受多个参数:
add = lambda a, b: a + b
add(3, 4) # 7何时使用 lambda:仅当它是一个在一个地方使用的小表达式时。如果它在增长,或你需要重复使用它,写一个适当的 def。跨越多个运算符或需要条件的 lambda 通常是切换到 def 的信号。
lambda 关键字编写。它存在的全部原因是作为 sorted() 的 key= 内联,这样你就不需要为一个任务定义单独的函数。一旦它变得长于一个整洁的表达式,我就写一个真正的 def 并感觉好多了。 列表推导式
Python 中最常见的转换:取一个序列,对每个项做某事,得到一个新的列表。列表推导式在一个可读的行中做这个:[expression for item in iterable]。你也可以用 if 添加一个过滤器。
长的方式:
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)列表推导式:
squares = [n ** 2 for n in numbers]结构总是相同的:[expression for item in iterable]。
scores = [87, 42, 96, 55, 71]
scaled = [s * 1.1 for s in scores] # 应用 10% 奖励
as_grades = [f"{s}/100" for s in scores] # 格式化每一个[expression for item in iterable] 取一个序列,对每个项做一件事,然后交给你一个新列表。从左到右阅读它,它确切地说出它做什么。这是第一个让我感觉我在写 Python 而不是从另一种语言翻译的 Python 特性。 用条件过滤
添加 if 子句以仅包括通过测试的项。结果是一个新列表,仅包含条件为 True 的项。
numbers = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [n for n in numbers if n % 2 == 0] # [2, 4, 6, 8]
odds = [n for n in numbers if n % 2 != 0] # [1, 3, 5, 7]scores = [87, 42, 96, 55, 71, 38]
passing = [s for s in scores if s >= 60] # [87, 96, 71]
failing = [s for s in scores if s < 60] # [42, 55, 38]if condition 以仅保留通过测试的项:[x for x in data if x > 0]。任何返回假的东西都会被排除在新列表之外。同一个推导式,它上面有一个守卫。 嵌套推导式
你可以嵌套推导式以将列表的列表展平为单个列表。从左到右阅读它:对于每一行,对于该行中的每一项,包括该项。
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]从左到右阅读它:对于矩阵中的每一行,对于行中的每一项,包括该项。
嵌套推导式很快就会变得混乱。如果解析需要超过一刻,显式地写循环。
[item for row in matrix for item in row]。从左到右阅读,外层循环第一,完全按照你会写循环的顺序。如果你的眼睛在它上面卡住了,那是你的提示,改为写真正的循环。 字典推导式
字典推导式在一个表达式中构建一个字典,使用与列表推导式相同的思想:{key: value for item in iterable}。用 if 添加过滤器,与列表推导式相同。
names = ["alice", "bob", "carol"]
scores = [87, 74, 92]
score_map = {name: score for name, score in zip(names, scores)}
# {"alice": 87, "bob": 74, "carol": 92}带有过滤器:
passing = {name: score for name, score in score_map.items() if score >= 80}
# {"alice": 87, "carol": 92}words = ["apple", "banana", "cherry"]
word_lens = {word: len(word) for word in words}
# {"apple": 5, "banana": 6, "cherry": 6}{key: value for item in iterable} 在一行中构建字典,形状与列表推导式相同,在键和值之间有冒号。将其与 .items() 配对以重塑你已拥有的字典,或与 zip() 配对以将两个列表缝合成一个映射。在末尾添加 `if` 以仅保留你想要的对。 集合推导式
集合推导式在一个表达式中构建一个集合,带有花括号和没有冒号。因为结果是一个集合,重复会自动移除。
words = ["apple", "banana", "cherry", "apple"]
unique = {w.lower() for w in words} # {"apple", "banana", "cherry"}当你想要唯一值且不关心顺序时,使用集合推导式。
{expr for item in iterable} 带有花括号和没有冒号构建一个集合,一个集合免费扔掉重复。所以如果你的工作是"给我唯一的那些",这在一行中做它。不过不要指望任何特定的顺序回来。 生成器表达式
生成器看起来像带有括号而不是方括号的列表推导式。关键区别:列表推导式一次在内存中构建整个列表。生成器一次产生一个值,仅在需要时产生。对于大序列,这使用远少的内存。
squares_gen = (n ** 2 for n in range(1000000))total = sum(n ** 2 for n in range(1000000)) # sum() 消费生成器当直接传递生成器到像 sum()、max()、min() 或 any() 的函数时,你可以丢弃额外的括号:
total = sum(n ** 2 for n in range(1000)) # 一组括号,不是两组对于大多数日常代码,列表推导式很好。当你处理大数据集或流式数据时使用生成器,其中在内存中保存所有东西会很浪费。
sum() 或 max() 并完全跳过构建列表。 zip()
zip() 将两个或更多序列的项配对在一起,所以你可以通过它们平行循环。它在最短序列处停止。这是避免在两个列表相互对应时管理索引的干净方式。
names = ["张三", "李四", "王五"]
scores = [87, 74, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# 张三: 87
# 李四: 74
# 王五: 92zip() 在最短序列处停止。如果你的序列可能是不同的长度,用填充值使用 itertools.zip_longest()。
要从配对的拉链列表转换回两个单独的列表,使用 zip(*pairs):
pairs = [("张三", 87), ("李四", 74), ("王五", 92)]
names, scores = zip(*pairs)
# names = ("张三", "李四", "王五")
# scores = (87, 74, 92)*pairs 将列表解包为单独的参数,所以 zip(*pairs) 变成 zip(("张三", 87), ("李四", 74), ("王五", 92))。* 运算符在函数章中覆盖。
zip() 也是在不手动管理索引的情况下平行迭代多个序列的干净方式:
before = [10, 20, 30]
after = [15, 18, 35]
for b, a in zip(before, after):
change = a - b
print(f"{b} -> {a} ({'+' if change >= 0 else ''}{change})")zip() 配对两个或更多序列,这样你可以循环它们一起,没有索引杂耍。它在最短处停止,所以不匹配的长度静默地丢失额外的。且 zip(*pairs) 反向运行它,将元组列表分裂回单独的列表。 map() 和 filter()
map() 和 filter() 是做推导式做的东西的较老函数式样式工具。你会在较老的代码中看到它们,所以值得知道它们是什么意思。优先考虑新代码的推导式;它们对大多数 Python 开发人员更可读。
numbers = [1, 2, 3, 4, 5]
list(map(lambda x: x ** 2, numbers)) # [1, 4, 9, 16, 25]
list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4]优先推导式;它们对大多数 Python 开发人员更可读。当你有一个已经存在的命名函数时使用 map():
strings = ["1", "2", "3"]
numbers = list(map(int, strings)) # [1, 2, 3](比推导式更干净)map(func, iterable) 在每个项上运行一个函数;filter(func, iterable) 仅保留函数来回真的项。它们是做推导式做的更老方式,所以你会在其他人的代码中遇到它们。对于你自己的,推导式对大多数人读得更清楚。 实践中
过滤玩家列表到通过得分,用 sorted 和 lambda 按得分排名,然后用枚举位置打印:
players = [
{"name": "张三", "score": 87},
{"name": "李四", "score": 42},
{"name": "王五", "score": 96},
{"name": "赵六", "score": 55},
]
passing = [p for p in players if p["score"] >= 60]
ranked = sorted(passing, key=lambda p: p["score"], reverse=True)
score_map = {p["name"]: p["score"] for p in ranked}
for i, (name, score) in enumerate(score_map.items(), start=1):
print(f"{i}. {name}: {score}")
