JEPA4Japan · 教程

迭代与推导式

1,482字 5分钟阅读 #Python

使用 enumerate、zip、sorted 和易读的推导式转换集合。

课程进度 课程大纲 已发布 24/24 课

一个循环,一项任务

第 7 章为你介绍了名为列表、字典和集合的盒子。现在,Python 可以依次走过每个盒子,完成一项小任务。

  1. 取出一个 Python 获取一个值
  2. 完成任务 循环体开始运行
  3. 继续前进 重复操作,直到完成
for 循环每次访问一个值。

直接获取你需要的值:

topics = ["Python", "Git", "SQL"]

for topic in topics:
    print("Practice", topic)

如果还需要一个用于显示的编号,请使用 enumerate():

for number, topic in enumerate(topics, start=1):
    print(number, topic)

输出:

1 Python
2 Git
3 SQL

start=1 会改变 enumerate() 生成的编号。它不会改变列表或列表中真正的索引。只有当数字索引本身就是任务的一部分时,才使用 range(len(topics))。

使用 zip() 配对两行数据

想象有两行卡片。zip() 先把两行中的第一张卡片夹在一起,再把第二张卡片夹在一起,依此类推。

  1. Python 55 分钟
  2. Git 25 分钟
  3. SQL 40 分钟
zip() 会把相同位置上的值配成一对。
topics = ["Python", "Git", "SQL"]
minutes = [55, 25, 40]

print(list(zip(topics, minutes)))

输出:

[('Python', 55), ('Git', 25), ('SQL', 40)]

这里有一个重要的边界情况:普通的 zip() 会在最短的输入结束时停止。

print(list(zip(["Python", "Git", "SQL"], [55, 25])))

输出:

[('Python', 55), ('Git', 25)]

SQL 悄悄消失了。如果这会导致数据出错,请使用 zip(topics, minutes, strict=True)。在 Python 3.10 或更新版本中,当长度不同时,它会引发 ValueError。第 10 章将教你如何处理这个错误。

排序,但不要弄乱原来的架子

sorted() 会创建一个新列表。原列表保持不变。

minutes = [55, 25, 40]
ranked = sorted(minutes, reverse=True)

print(ranked)
print(minutes)

输出:

[55, 40, 25]
[55, 25, 40]

当 Python 应该比较每条记录中的某一部分时,可以使用一个简短的键函数:

def minutes_in(record):
    return record["minutes"]


records = [
    {"topic": "Python", "minutes": 40},
    {"topic": "Git", "minutes": 25},
    {"topic": "SQL", "minutes": 40},
]

print([record["topic"] for record in sorted(records, key=minutes_in, reverse=True)])

输出:

['Python', 'SQL', 'Git']

Python 的排序是稳定的:Python 和 SQL 的值相同,因此它们会保持原来的先后顺序。请以 key=minutes_in 的形式传入函数,不要加 ()。

简短提醒:minutes.sort() 会修改该列表,并返回 None。当你想要一个新结果,并且必须保持原来的架子不变时,请使用 sorted(minutes)。

推导式是小巧的集合制造机

  1. 原集合 20、35、50
  2. 可选的筛选条件 保留 30 或更大的值
  3. 新集合 35、50
推导式只需清晰地遍历一次,就能构建一个新集合。
minutes = [20, 35, 50]

long_sessions = [value for value in minutes if value >= 30]
topic_names = {name.strip().casefold() for name in [" Python ", "python", "Git"]}
lookup = {topic: value for topic, value in zip(["Python", "Git"], [20, 35])}

print(long_sessions)
print(sorted(topic_names))
print(lookup)

输出:

[35, 50]
['git', 'python']
{'Python': 20, 'Git': 35}

方括号创建列表;包含一个表达式的花括号创建集合;key: value 创建字典。好的推导式只包含一个显而易见的转换,或许再加一个简单的筛选条件。如果需要做出多项判断、输出消息或执行多次更新,请把它展开成普通循环。易于阅读比代码简短更重要。

创建一支学习巡游队伍

将这个完整项目保存为 study_parade.py。它会严格配对数据、对一个新列表排序、为结果编号,并检查源数据是否未被修改。

def make_records(topics, minutes):
    return [
        {"topic": topic, "minutes": duration}
        for topic, duration in zip(topics, minutes, strict=True)
    ]


def minutes_in(record):
    return record["minutes"]


def build_report(topics, minutes, target):
    ranked = sorted(make_records(topics, minutes), key=minutes_in, reverse=True)
    lines = []

    for number, record in enumerate(ranked, start=1):
        if record["minutes"] >= target:
            status = "target met"
        else:
            status = "keep going"
        lines.append(
            f"{number}. {record['topic']}: "
            f"{record['minutes']} minutes ({status})"
        )

    return lines


topics = ["Python", "Git", "SQL", "Testing"]
minutes = [55, 25, 40, 25]
topics_before = topics.copy()

report = build_report(topics, minutes, target=30)

assert topics == topics_before
assert report[0].startswith("1. Python")

print("Study parade")
for line in report:
    print(line)
print("Source still:", topics)

运行 python3 study_parade.py。你应该会看到:

Study parade
1. Python: 55 minutes (target met)
2. SQL: 40 minutes (target met)
3. Git: 25 minutes (keep going)
4. Testing: 25 minutes (keep going)
Source still: ['Python', 'Git', 'SQL', 'Testing']

Git 仍排在 Testing 前面,因为它们的值相同,并且排序是稳定的。如果出现 AssertionError,就说明之前保证的某个事实已经不再成立。

三个小任务

  1. 使用 enumerate(start=1) 为四种零食的名称编号。
  2. 尝试对三个名称和两个分数使用普通的 zip();然后添加 strict=True,并记录错误类型。
  3. 在项目中添加一次 60 分钟的学习活动,然后使用推导式,只收集达到目标的主题。

准备好学习第 9 章了吗?

  • 我可以直接循环遍历值。
  • 我可以使用 enumerate() 添加便于人们阅读的编号。
  • 我可以解释为什么普通的 zip() 可能会丢掉一个元素。
  • 我可以使用 sorted(),而不修改源数据。
  • 我可以保持推导式简洁,并在它变得拥挤时改用循环。
  • 我运行了学习巡游项目,并看到所有断言都通过了。

接下来,你会把这些任务放进一些相互协作的小函数中,并学习每个函数可以在哪里看到它所使用的名称。